1use std::collections::HashMap;
11use std::collections::BinaryHeap;
12use std::fmt::{self, Debug, Formatter};
13use std::ops::Range;
14use std::sync::{Mutex, RwLock};
15use dashmap::DashMap;
16use g_math::fixed_point::{FixedPoint, FixedVector};
17use super::hyperbolic_geometry::{HyperbolicPoint, ratio_to_distance};
18use super::hash_table::{GeometricSignature, HyperbolicHashTable};
19use super::klein::{self, KleinPoint, PowerCell, PointLocationGrid};
20use crate::constants;
21use crate::metric_tree::EuclideanMetric;
22use crate::semantic_index::SemanticIndexCache;
23
24#[derive(Clone, Debug)]
26pub struct NodeMetadata {
27 pub key: String,
29 pub content_type: Option<String>,
31 pub metadata: HashMap<String, String>,
33 pub created_at: u64,
35 pub updated_at: u64,
37}
38
39impl NodeMetadata {
40 pub fn new(key: String, content_type: Option<String>) -> Self {
42 let now = std::time::SystemTime::now()
43 .duration_since(std::time::UNIX_EPOCH)
44 .unwrap_or_default()
45 .as_secs();
46
47 Self {
48 key,
49 content_type,
50 metadata: HashMap::new(),
51 created_at: now,
52 updated_at: now,
53 }
54 }
55
56 pub fn touch(&mut self) {
58 self.updated_at = std::time::SystemTime::now()
59 .duration_since(std::time::UNIX_EPOCH)
60 .unwrap_or_default()
61 .as_secs();
62 }
63}
64
65pub const RAINBOW_BAND_CAPACITY: u32 = 256;
70const RAINBOW_BAND_STEP_DIV: i32 = 64;
72const RAINBOW_BAND_WARN: u32 = 32;
74
75#[derive(Clone, Debug)]
81pub struct CompressedNode {
82 node_metadata: NodeMetadata,
84 value: Vec<u8>,
86 children: Vec<GeometricSignature>,
88 semantic_coords: Vec<u8>,
91}
92
93impl CompressedNode {
94 pub fn new(metadata: NodeMetadata, value: Vec<u8>) -> Self {
96 Self {
97 node_metadata: metadata,
98 value,
99 children: Vec::new(),
100 semantic_coords: Vec::new(),
101 }
102 }
103
104 pub fn metadata(&self) -> &NodeMetadata {
106 &self.node_metadata
107 }
108
109 pub fn metadata_mut(&mut self) -> &mut NodeMetadata {
111 &mut self.node_metadata
112 }
113
114 pub fn value(&self) -> &[u8] {
116 &self.value
117 }
118
119 pub fn update_value(&mut self, value: Vec<u8>) {
121 self.value = value;
122 self.node_metadata.touch();
123 }
124
125 pub fn add_child(&mut self, signature: GeometricSignature) {
127 self.children.push(signature);
128 }
129
130 pub fn remove_child(&mut self, unique_id: &str) {
132 self.children.retain(|sig| sig.unique_id() != unique_id);
133 }
134
135 pub fn children(&self) -> &[GeometricSignature] {
137 &self.children
138 }
139
140 pub fn has_children(&self) -> bool {
142 !self.children.is_empty()
143 }
144
145 pub fn child_count(&self) -> usize {
147 self.children.len()
148 }
149
150 pub fn semantic_coords(&self) -> &[u8] {
152 &self.semantic_coords
153 }
154
155 pub fn set_semantic_coords(&mut self, coords: Vec<u8>) {
157 self.semantic_coords = coords;
158 }
159}
160
161pub struct HyperbolicTensorNetwork {
171 hash_table: HyperbolicHashTable,
173 nodes: DashMap<String, CompressedNode>,
175 point_map: DashMap<String, HyperbolicPoint>,
177 root_signature: Mutex<Option<GeometricSignature>>,
179 tau: FixedPoint,
181 child_counts: DashMap<String, u32>,
183 klein_points: DashMap<String, KleinPoint>,
185 power_cells: DashMap<String, PowerCell>,
187 point_location: RwLock<PointLocationGrid>,
189 semantic_index: SemanticIndexCache,
192}
193
194impl HyperbolicTensorNetwork {
195 const DEFAULT_GRID_RESOLUTION: usize = 64;
197
198 pub fn new(dimension: usize, tau: FixedPoint) -> Self {
200 Self::with_grid_resolution(dimension, tau, Self::DEFAULT_GRID_RESOLUTION)
201 }
202
203 pub fn with_grid_resolution(dimension: usize, tau: FixedPoint, grid_resolution: usize) -> Self {
205 assert_eq!(
210 FixedPoint::raw_byte_len(), 16,
211 "horon-engine requires the 16-byte Q64.64 g_math profile (GMATH_PROFILE=embedded); \
212 rebuild with the correct profile"
213 );
214
215 let hash_table = HyperbolicHashTable::new(dimension);
216
217 Self {
218 hash_table,
219 nodes: DashMap::new(),
220 point_map: DashMap::new(),
221 root_signature: Mutex::new(None),
222 tau,
223 child_counts: DashMap::new(),
224 klein_points: DashMap::new(),
225 power_cells: DashMap::new(),
226 point_location: RwLock::new(PointLocationGrid::with_dimension(grid_resolution, dimension)),
227 semantic_index: SemanticIndexCache::new(),
228 }
229 }
230
231 pub fn add_node_data_only(&self, metadata: NodeMetadata, value: Vec<u8>, _level: u32) -> String {
238 use sha3::{Sha3_256, Digest as _};
239 let mut hasher = Sha3_256::new();
240 hasher.update(b"data_only:");
241 hasher.update(metadata.key.as_bytes());
242 let unique_id = hex::encode(&hasher.finalize()[..16]);
243
244 let node = CompressedNode::new(metadata, value);
245 self.nodes.insert(unique_id.clone(), node);
246 self.semantic_index.bump();
249 unique_id
250 }
251
252 pub fn add_node(&self,
258 metadata: NodeMetadata,
259 value: Vec<u8>,
260 parent_signature: Option<&GeometricSignature>,
261 level: u32) -> Option<GeometricSignature> {
262 self.add_node_inner(metadata, value, parent_signature, level, None)
263 }
264
265 pub fn add_node_positioned(&self,
270 metadata: NodeMetadata,
271 value: Vec<u8>,
272 parent_signature: Option<&GeometricSignature>,
273 level: u32,
274 child_index: u32) -> Option<GeometricSignature> {
275 self.add_node_inner(metadata, value, parent_signature, level, Some(child_index))
276 }
277
278 fn add_node_inner(&self,
279 metadata: NodeMetadata,
280 value: Vec<u8>,
281 parent_signature: Option<&GeometricSignature>,
282 level: u32,
283 child_index_hint: Option<u32>) -> Option<GeometricSignature> {
284 if self.tau > FixedPoint::from_int(0) {
288 let depth_budget = (FixedPoint::from_int(44) / self.tau).to_int() as u32;
289 if level.saturating_mul(10) >= depth_budget.saturating_mul(9) {
290 log::warn!(
291 "insert '{}' at depth {} approaches the Q64.64 precision budget (~{} levels at tau={}); sibling positions may lose separation",
292 metadata.key, level, depth_budget, self.tau.to_f64()
293 );
294 }
295 }
296 const MAX_PROBE: u32 = 1024;
317 let mut probe = child_index_hint;
318 let mut resolved = None;
319
320 for _ in 0..MAX_PROBE {
321 let (point, child_index) = match parent_signature {
322 Some(parent_sig) => self.compute_child_placement(parent_sig, probe),
323 None => (self.hash_table.poincare_disk().origin(), 0),
324 };
325
326 let signature = self.hash_table.create_signature(&point, level)?;
327 let unique_id = signature.unique_id();
328
329 let taken_by_other = self
331 .nodes
332 .get(&unique_id)
333 .map(|existing| existing.metadata().key != metadata.key)
334 .unwrap_or(false);
335
336 if !taken_by_other {
337 resolved = Some((point, child_index, signature, unique_id));
338 break;
339 }
340
341 if parent_signature.is_none() {
344 break;
345 }
346 probe = Some(child_index.saturating_add(1));
347 }
348
349 let Some((point, child_index, signature, unique_id)) = resolved else {
350 log::error!(
351 "could not place '{}': no free sibling slot within {} probes — \
352 precision budget exceeded (depth/fan-out); insert refused",
353 metadata.key, MAX_PROBE
354 );
355 return None;
356 };
357
358 if let Some(parent_sig) = parent_signature {
362 self.commit_child_index(&parent_sig.unique_id(), Some(child_index), child_index);
367 }
368
369 let node = CompressedNode::new(metadata, value);
370 self.nodes.insert(unique_id.clone(), node);
371 self.semantic_index.bump();
374
375 if let Some(mut node_ref) = self.nodes.get_mut(&unique_id) {
377 node_ref.metadata_mut().metadata.insert(
378 "_child_index".to_string(),
379 child_index.to_string(),
380 );
381 }
382 self.point_map.insert(unique_id.clone(), point.clone());
383
384 self.hash_table.register_node_with_hint(&point, &unique_id, level, Some(signature.hash()));
387
388 let klein_pt = klein::poincare_to_klein(&point);
390 self.klein_points.insert(unique_id.clone(), klein_pt.clone());
391
392 if let Some(parent_sig) = parent_signature {
393 let parent_id = parent_sig.unique_id();
394
395 if let Some(parent_klein) = self.klein_points.get(&parent_id).map(|r| r.value().clone()) {
397 let hp_leaf = klein::compute_bisector(&klein_pt, &parent_klein, &parent_id);
399 let hp_parent = klein::compute_bisector(&parent_klein, &klein_pt, &unique_id);
401
402 self.power_cells.insert(unique_id.clone(), PowerCell {
404 node_id: unique_id.clone(),
405 site: klein_pt.clone(),
406 half_planes: vec![hp_leaf],
407 });
408
409 if let Some(mut parent_cell) = self.power_cells.get_mut(&parent_id) {
411 parent_cell.half_planes.push(hp_parent);
412 }
413
414 self.point_location.write().unwrap_or_else(|e| e.into_inner()).update_insert(&parent_id, &unique_id, &klein_pt, &parent_klein);
416 }
417 } else {
418 self.power_cells.insert(unique_id.clone(), PowerCell {
420 node_id: unique_id.clone(),
421 site: klein_pt.clone(),
422 half_planes: Vec::new(),
423 });
424
425 let sites: Vec<(String, KleinPoint)> = vec![
427 (unique_id.clone(), klein_pt.clone()),
428 ];
429 self.point_location.write().unwrap_or_else(|e| e.into_inner()).build(&sites);
430 }
431 if parent_signature.is_none() {
434 let mut root = self.root_signature.lock().unwrap_or_else(|e| e.into_inner());
435 if root.is_none() {
436 *root = Some(signature.clone());
437 }
438 }
439
440 if let Some(parent_sig) = parent_signature {
441 if let Some(mut parent_node) = self.nodes.get_mut(&parent_sig.unique_id()) {
442 parent_node.add_child(signature.clone());
443 }
444 }
445
446 Some(signature)
447 }
448
449 fn compute_child_placement(&self, parent_signature: &GeometricSignature, child_index_hint: Option<u32>) -> (HyperbolicPoint, u32) {
467 let parent_id = parent_signature.unique_id();
468 let dimension = self.hash_table.poincare_disk().dimension();
469
470 let parent_point = self.point_map.get(&parent_id)
472 .map(|r| r.value().clone())
473 .unwrap_or_else(|| HyperbolicPoint::origin(dimension));
474
475 let child_index = child_index_hint
478 .unwrap_or_else(|| self.child_counts.get(&parent_id).map(|r| *r.value()).unwrap_or(0));
479
480 let band = child_index / RAINBOW_BAND_CAPACITY;
495 if band >= RAINBOW_BAND_WARN {
496 log::warn!(
497 "parent of child '{}' reached rainbow band {} ({}+ siblings): placement \
498 remains collision-free but subtree spacing is degrading — consider restructuring",
499 child_index, band, child_index
500 );
501 }
502 let effective_tau = self.tau
503 + self.tau * FixedPoint::from_int(band as i32)
504 / FixedPoint::from_int(RAINBOW_BAND_STEP_DIV);
505 let half_tau = effective_tau / FixedPoint::from_int(2);
506 let r = half_tau.tanh();
507
508 let angle = FixedPoint::from_int(child_index as i32) * constants::golden_angle();
510
511 let mut child_at_origin = FixedVector::new(dimension);
513 if dimension >= 2 {
514 let (sin_a, cos_a) = angle.sincos();
515 child_at_origin[0] = r * cos_a;
516 child_at_origin[1] = r * sin_a;
517 } else {
519 child_at_origin[0] = if child_index % 2 == 0 { r } else { -r };
521 }
522 let child_point = HyperbolicPoint::new(child_at_origin);
523
524 (child_point.reflect_from_origin(&parent_point), child_index)
526 }
527
528 fn commit_child_index(&self, parent_id: &str, child_index_hint: Option<u32>, child_index: u32) {
534 let next = match child_index_hint {
535 Some(hint) => {
536 let current = self.child_counts.get(parent_id).map(|r| *r.value()).unwrap_or(0);
537 current.max(hint + 1)
538 }
539 None => child_index + 1,
540 };
541 self.child_counts.insert(parent_id.to_string(), next);
542 }
543
544 pub fn get_node_by_signature(&self, signature: &GeometricSignature) -> Option<CompressedNode> {
546 self.nodes.get(&signature.unique_id()).map(|r| r.value().clone())
547 }
548
549 pub fn update_node_value(&self, unique_id: &str, value: Vec<u8>) -> bool {
551 if let Some(mut node) = self.nodes.get_mut(unique_id) {
552 node.update_value(value);
553 true
554 } else {
555 false
556 }
557 }
558
559 pub fn set_node_metadata_entry(&self, unique_id: &str, key: &str, val: &str) -> bool {
561 if let Some(mut node) = self.nodes.get_mut(unique_id) {
562 node.metadata_mut().metadata.insert(key.to_string(), val.to_string());
563 true
564 } else {
565 false
566 }
567 }
568
569 pub fn set_node_semantic(&self, unique_id: &str, coords: Vec<u8>) -> bool {
571 if let Some(mut node) = self.nodes.get_mut(unique_id) {
572 node.set_semantic_coords(coords);
573 drop(node); self.semantic_index.bump();
577 true
578 } else {
579 false
580 }
581 }
582
583 pub fn get_node_semantic(&self, unique_id: &str) -> Option<Vec<u8>> {
585 self.nodes.get(unique_id).map(|node| node.semantic_coords().to_vec())
586 }
587
588 pub fn root_node(&self) -> Option<CompressedNode> {
590 let root = self.root_signature.lock().unwrap_or_else(|e| e.into_inner());
591 root.as_ref().and_then(|sig| {
592 self.get_node_by_signature(sig)
593 })
594 }
595
596 pub fn root_signature(&self) -> Option<GeometricSignature> {
598 self.root_signature.lock().unwrap_or_else(|e| e.into_inner()).clone()
599 }
600
601 pub fn children_of(&self, signature: &GeometricSignature) -> Vec<CompressedNode> {
603 let node = match self.nodes.get(&signature.unique_id()) {
604 Some(r) => r.value().clone(),
605 None => return Vec::new(),
606 };
607
608 let mut children = Vec::new();
609 for child_sig in node.children() {
610 if let Some(child) = self.nodes.get(&child_sig.unique_id()) {
611 children.push(child.value().clone());
612 }
613 }
614
615 children
616 }
617
618 pub fn get_point(&self, unique_id: &str) -> Option<HyperbolicPoint> {
620 self.point_map.get(unique_id).map(|r| r.value().clone())
621 }
622
623 pub fn semantic_epoch(&self) -> u64 {
628 self.semantic_index.epoch()
629 }
630
631 pub fn hash_table(&self) -> &HyperbolicHashTable {
633 &self.hash_table
634 }
635
636 pub fn node_count(&self) -> usize {
638 self.nodes.len()
639 }
640
641 pub fn remove_detached_node(&self, unique_id: &str) {
646 self.nodes.remove(unique_id);
647 self.semantic_index.bump();
649 }
650
651 pub fn unregister_node(&self, unique_id: &str) {
657 self.unregister_node_with_parent(unique_id, None)
658 }
659
660 pub fn unregister_node_with_parent(&self, unique_id: &str, parent_uid: Option<&str>) {
664 let parent_id: Option<String> = self.power_cells.get(unique_id)
667 .and_then(|cell| cell.half_planes.first().map(|hp| hp.neighbor_id.clone()));
668
669 if let Some(ref pid) = parent_id {
670 if let Some(mut parent_cell) = self.power_cells.get_mut(pid) {
672 parent_cell.half_planes.retain(|hp| hp.neighbor_id != unique_id);
673 }
674
675 self.point_location.write().unwrap_or_else(|e| e.into_inner()).update_delete(unique_id, pid);
677 }
678
679 self.klein_points.remove(unique_id);
680 self.power_cells.remove(unique_id);
681 self.child_counts.remove(unique_id);
682 self.hash_table.unregister_node(unique_id);
685 self.point_map.remove(unique_id);
686
687 self.nodes.remove(unique_id);
691 self.semantic_index.bump();
693
694 if let Some(pid) = parent_uid.map(str::to_string).or(parent_id) {
696 if let Some(mut parent_node) = self.nodes.get_mut(&pid) {
697 parent_node.remove_child(unique_id);
698 }
699 }
700
701 let mut root = self.root_signature.lock().unwrap_or_else(|e| e.into_inner());
703 if root.as_ref().map(|s| s.unique_id()).as_deref() == Some(unique_id) {
704 *root = None;
705 }
706 }
707
708 pub fn find_descendants_spatial(&self, signature: &GeometricSignature) -> Vec<(String, FixedPoint)> {
713 let unique_id = signature.unique_id();
714 let point = match self.point_map.get(&unique_id) {
715 Some(r) => r.value().clone(),
716 None => return Vec::new(),
717 };
718
719 let subtree_radius = FixedPoint::from_int(3) * self.tau;
721
722 self.hash_table.find_nodes_in_radius(&point, subtree_radius)
723 .into_iter()
724 .filter(|(uid, _)| *uid != unique_id)
725 .collect()
726 }
727
728 pub fn nearest_neighbor_point(&self, query_poincare: &HyperbolicPoint) -> Option<(String, FixedPoint)> {
744 if self.klein_points.is_empty() {
745 return None;
746 }
747
748 const VERIFY_K: usize = 5;
750
751 let query_klein = klein::poincare_to_klein(query_poincare);
752
753 let grid_candidate = self.point_location.read().unwrap_or_else(|e| e.into_inner()).query(&query_klein.coords).map(|s| s.to_string());
755 if let Some(candidate_id) = grid_candidate.as_deref() {
756 let cell_data = self.power_cells.get(candidate_id).map(|r| r.value().clone());
758
759 if let Some(cell) = cell_data {
760 let n_neighbors = cell.half_planes.len();
761
762 if n_neighbors <= VERIFY_K {
763 let mut best_id = candidate_id.to_string();
765 let mut best_ratio = self.point_map.get(candidate_id)
766 .map(|p| query_poincare.hyperbolic_ratio(p.value()))
767 .unwrap_or(FixedPoint::from_int(1));
768
769 for hp in &cell.half_planes {
770 if let Some(neighbor_point) = self.point_map.get(&hp.neighbor_id) {
771 let r = query_poincare.hyperbolic_ratio(neighbor_point.value());
772 if r < best_ratio {
773 best_ratio = r;
774 best_id = hp.neighbor_id.clone();
775 }
776 }
777 }
778
779 return Some((best_id, ratio_to_distance(best_ratio)));
784 }
785
786 let candidate_klein = self.klein_points.get(candidate_id).map(|r| r.value().clone());
788 let mut pd_ranked: Vec<(String, FixedPoint)> = Vec::with_capacity(n_neighbors + 1);
789
790 if let Some(ref ck) = candidate_klein {
791 pd_ranked.push((candidate_id.to_string(), klein::power_distance(&query_klein.coords, ck)));
792 }
793
794 for hp in &cell.half_planes {
795 if let Some(nk) = self.klein_points.get(&hp.neighbor_id) {
796 pd_ranked.push((hp.neighbor_id.clone(), klein::power_distance(&query_klein.coords, nk.value())));
797 }
798 }
799
800 pd_ranked.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
801
802 let mut best_id = String::new();
804 let mut best_ratio = FixedPoint::from_int(1);
805
806 for (id, _pd) in pd_ranked.iter().take(VERIFY_K) {
807 if let Some(point) = self.point_map.get(id) {
808 let r = query_poincare.hyperbolic_ratio(point.value());
809 if r < best_ratio {
810 best_ratio = r;
811 best_id = id.clone();
812 }
813 }
814 }
815
816 if !best_id.is_empty() {
817 return Some((best_id, ratio_to_distance(best_ratio)));
819 }
820 } else {
821 if let Some(candidate_point) = self.point_map.get(candidate_id) {
822 let dist = query_poincare.hyperbolic_distance(candidate_point.value());
823 return Some((candidate_id.to_string(), dist));
824 }
825 }
826 }
827
828 let results = self.hash_table.find_nearest_nodes(query_poincare, 1);
830 if let Some((id, dist)) = results.into_iter().next() {
831 return Some((id, dist));
832 }
833
834 None
835 }
836
837 pub fn nearest_neighbor_point_k(&self, query_poincare: &HyperbolicPoint, k: usize) -> Vec<(String, FixedPoint)> {
842 if self.klein_points.is_empty() || k == 0 {
843 return Vec::new();
844 }
845
846 let query_klein = klein::poincare_to_klein(query_poincare);
847
848 let mut candidates: Vec<(String, FixedPoint)> = Vec::new();
850
851 let grid_candidate = self.point_location.read().unwrap_or_else(|e| e.into_inner())
852 .query(&query_klein.coords).map(|s| s.to_string());
853
854 if let Some(candidate_id) = grid_candidate.as_deref() {
855 if let Some(point) = self.point_map.get(candidate_id) {
857 let dist = query_poincare.hyperbolic_distance(point.value());
858 candidates.push((candidate_id.to_string(), dist));
859 }
860
861 if let Some(cell) = self.power_cells.get(candidate_id).map(|r| r.value().clone()) {
863 for hp in &cell.half_planes {
864 if let Some(point) = self.point_map.get(&hp.neighbor_id) {
865 let dist = query_poincare.hyperbolic_distance(point.value());
866 candidates.push((hp.neighbor_id.clone(), dist));
867 }
868 }
869 }
870 }
871
872 let vp_results = self.hash_table.find_nearest_nodes(query_poincare, k + candidates.len());
874 for (id, dist) in vp_results {
875 if !candidates.iter().any(|(cid, _)| cid == &id) {
876 candidates.push((id, dist));
877 }
878 }
879
880 candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
881 candidates.truncate(k);
882 candidates
883 }
884
885 pub fn get_klein_point(&self, unique_id: &str) -> Option<KleinPoint> {
887 self.klein_points.get(unique_id).map(|r| r.value().clone())
888 }
889
890 pub fn get_power_cell(&self, unique_id: &str) -> Option<PowerCell> {
892 self.power_cells.get(unique_id).map(|r| r.value().clone())
893 }
894
895 pub fn grid_assigned_tile_count(&self) -> usize {
897 self.point_location.read().unwrap_or_else(|e| e.into_inner()).assigned_tile_count()
898 }
899
900 pub fn semantic_distance(
915 coords_a: &[u8],
916 coords_b: &[u8],
917 dim_range: &Range<usize>,
918 ) -> FixedPoint {
919 let a = Self::decode_semantic_slice(coords_a, dim_range);
920 let b = Self::decode_semantic_slice(coords_b, dim_range);
921 g_math::fixed_point::imperative::fused::euclidean_distance(&a, &b)
922 }
923
924 pub fn decode_semantic_slice(coords: &[u8], dim_range: &Range<usize>) -> Vec<FixedPoint> {
929 dim_range
930 .clone()
931 .map(|dim| {
932 let start = dim * 16;
933 let end = start + 16;
934 if coords.len() >= end {
935 FixedPoint::from_raw(i128::from_le_bytes(
936 coords[start..end].try_into().unwrap(),
937 ))
938 } else {
939 FixedPoint::from_int(0)
940 }
941 })
942 .collect()
943 }
944
945 pub fn nearest_semantic(
963 &self,
964 query_coords: &[u8],
965 k: usize,
966 dim_range: &Range<usize>,
967 ) -> Vec<(String, FixedPoint)> {
968 if k == 0 {
969 return Vec::new();
970 }
971
972 if self.nodes.len() < constants::SEMANTIC_INDEX_MIN_NODES {
973 return self.nearest_semantic_scan(query_coords, k, dim_range);
974 }
975
976 let query = Self::decode_semantic_slice(query_coords, dim_range);
977 let index = self.semantic_index.get_or_build(dim_range, || {
978 self.nodes
979 .iter()
980 .filter(|entry| !entry.value().semantic_coords().is_empty())
981 .map(|entry| {
982 (
983 entry.value().metadata().key.clone(),
984 Self::decode_semantic_slice(entry.value().semantic_coords(), dim_range),
985 )
986 })
987 .collect()
988 });
989 index.tree.knn(&query, k, &EuclideanMetric)
990 }
991
992 pub fn nearest_semantic_scan(
999 &self,
1000 query_coords: &[u8],
1001 k: usize,
1002 dim_range: &Range<usize>,
1003 ) -> Vec<(String, FixedPoint)> {
1004 if k == 0 {
1005 return Vec::new();
1006 }
1007
1008 let mut heap: BinaryHeap<(FixedPoint, String)> = BinaryHeap::new();
1012
1013 for entry in self.nodes.iter() {
1014 let coords = entry.value().semantic_coords();
1015
1016 if coords.is_empty() {
1018 continue;
1019 }
1020
1021 let dist = Self::semantic_distance(query_coords, coords, dim_range);
1022 let key = entry.value().metadata().key.as_str();
1023
1024 if heap.len() < k {
1025 heap.push((dist, key.to_string()));
1026 } else if let Some(worst) = heap.peek() {
1027 if (dist, key) < (worst.0, worst.1.as_str()) {
1028 heap.pop();
1029 heap.push((dist, key.to_string()));
1030 }
1031 }
1032 }
1033
1034 let mut results: Vec<(String, FixedPoint)> = heap
1036 .into_iter()
1037 .map(|(dist, uid)| (uid, dist))
1038 .collect();
1039 results.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
1040 results
1041 }
1042
1043 pub fn validate_network(&self) -> bool {
1048 if self.nodes.is_empty() {
1049 return false;
1050 }
1051
1052 let root_sig = self.root_signature.lock().unwrap_or_else(|e| e.into_inner());
1053 if root_sig.is_none() {
1054 return false;
1055 }
1056
1057 let root_id = root_sig.as_ref().unwrap().unique_id();
1058 drop(root_sig);
1059 if !self.nodes.contains_key(&root_id) {
1060 return false;
1061 }
1062
1063 for entry in self.nodes.iter() {
1065 let node = entry.value();
1066 for child_sig in node.children() {
1067 if !self.nodes.contains_key(&child_sig.unique_id()) {
1068 return false;
1069 }
1070 }
1071 }
1072
1073 for entry in self.nodes.iter() {
1075 if !self.point_map.contains_key(entry.key()) {
1076 return false;
1077 }
1078 }
1079
1080 for entry in self.point_map.iter() {
1082 if !self.nodes.contains_key(entry.key()) {
1083 return false;
1084 }
1085 }
1086
1087 for entry in self.klein_points.iter() {
1089 if !self.nodes.contains_key(entry.key()) {
1090 return false;
1091 }
1092 }
1093
1094 for entry in self.power_cells.iter() {
1096 if !self.nodes.contains_key(entry.key()) {
1097 return false;
1098 }
1099 }
1100
1101 for entry in self.power_cells.iter() {
1103 for hp in &entry.value().half_planes {
1104 if !self.nodes.contains_key(&hp.neighbor_id) {
1105 return false;
1106 }
1107 }
1108 }
1109
1110 for entry in self.child_counts.iter() {
1112 if !self.nodes.contains_key(entry.key()) {
1113 return false;
1114 }
1115 }
1116
1117 true
1118 }
1119}
1120
1121impl Debug for HyperbolicTensorNetwork {
1122 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1123 write!(f, "HyperbolicTensorNetwork(nodes={}, dimension={})",
1124 self.nodes.len(),
1125 self.hash_table.poincare_disk().dimension())
1126 }
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131 use super::*;
1132
1133 #[test]
1134 fn test_compressed_node() {
1135 let metadata = NodeMetadata::new("test".to_string(), None);
1136 let value = b"Node data".to_vec();
1137
1138 let node = CompressedNode::new(metadata, value.clone());
1139
1140 assert_eq!(node.metadata().key, "test");
1141 assert_eq!(node.value(), &value[..]);
1142 assert!(!node.has_children());
1143 assert_eq!(node.child_count(), 0);
1144 }
1145
1146 #[test]
1147 fn test_tensor_network_creation() {
1148 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1149
1150 assert_eq!(network.node_count(), 0);
1151 assert!(network.root_node().is_none());
1152 }
1153
1154 #[test]
1155 fn test_adding_nodes() {
1156 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1157
1158 let root_meta = NodeMetadata::new("/".to_string(), None);
1159 let root_sig = network.add_node(
1160 root_meta,
1161 b"Root node data".to_vec(),
1162 None,
1163 0
1164 ).unwrap();
1165
1166 assert_eq!(network.node_count(), 1);
1167 assert!(network.root_node().is_some());
1168
1169 let child_meta = NodeMetadata::new("/child".to_string(), None);
1170 let child_sig = network.add_node(
1171 child_meta,
1172 b"Child node data".to_vec(),
1173 Some(&root_sig),
1174 1
1175 ).unwrap();
1176
1177 assert_eq!(network.node_count(), 2);
1178
1179 let root_node = network.get_node_by_signature(&root_sig).unwrap();
1180 assert_eq!(root_node.child_count(), 1);
1181 assert_eq!(root_node.children()[0].unique_id(), child_sig.unique_id());
1182 }
1183
1184 #[test]
1185 fn test_network_validation() {
1186 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1187
1188 assert!(!network.validate_network());
1189
1190 let root_sig = network.add_node(
1191 NodeMetadata::new("/".to_string(), None),
1192 b"Root data".to_vec(),
1193 None,
1194 0
1195 ).unwrap();
1196
1197 assert!(network.validate_network());
1198
1199 network.add_node(
1200 NodeMetadata::new("/child1".to_string(), None),
1201 b"Child 1 data".to_vec(),
1202 Some(&root_sig),
1203 1
1204 ).unwrap();
1205
1206 network.add_node(
1207 NodeMetadata::new("/child2".to_string(), None),
1208 b"Child 2 data".to_vec(),
1209 Some(&root_sig),
1210 1
1211 ).unwrap();
1212
1213 assert!(network.validate_network());
1214 }
1215
1216 #[test]
1217 fn test_point_map() {
1218 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1219
1220 let root_sig = network.add_node(
1221 NodeMetadata::new("/".to_string(), None),
1222 b"root".to_vec(),
1223 None,
1224 0
1225 ).unwrap();
1226
1227 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1229 assert!(root_point.euclidean_norm() < constants::epsilon());
1230
1231 let child_sig = network.add_node(
1232 NodeMetadata::new("/child".to_string(), None),
1233 b"child".to_vec(),
1234 Some(&root_sig),
1235 1
1236 ).unwrap();
1237
1238 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1240 assert!(child_point.euclidean_norm() > constants::epsilon());
1241 }
1242
1243 #[test]
1244 fn test_spatial_descendants() {
1245 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1246
1247 let root_sig = network.add_node(
1248 NodeMetadata::new("/".to_string(), None),
1249 b"root".to_vec(),
1250 None,
1251 0
1252 ).unwrap();
1253
1254 let child_sig = network.add_node(
1255 NodeMetadata::new("/child".to_string(), None),
1256 b"child".to_vec(),
1257 Some(&root_sig),
1258 1
1259 ).unwrap();
1260
1261 let _grandchild_sig = network.add_node(
1262 NodeMetadata::new("/child/grandchild".to_string(), None),
1263 b"grandchild".to_vec(),
1264 Some(&child_sig),
1265 2
1266 ).unwrap();
1267
1268 let descendants = network.find_descendants_spatial(&root_sig);
1270 assert!(descendants.len() >= 2,
1271 "Expected at least 2 descendants, got {}", descendants.len());
1272 }
1273
1274 #[test]
1275 fn test_sarkar_child_distance() {
1276 let tau = constants::default_tau();
1278 let network = HyperbolicTensorNetwork::new(2, tau);
1279
1280 let root_sig = network.add_node(
1281 NodeMetadata::new("/".to_string(), None),
1282 b"root".to_vec(),
1283 None,
1284 0
1285 ).unwrap();
1286
1287 let child_sig = network.add_node(
1288 NodeMetadata::new("/child".to_string(), None),
1289 b"child".to_vec(),
1290 Some(&root_sig),
1291 1
1292 ).unwrap();
1293
1294 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1295 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1296
1297 let dist = root_point.hyperbolic_distance(&child_point);
1298 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1299 assert!((dist - tau).abs() < tolerance,
1300 "Child should be at distance τ={} from parent, got {}", tau, dist);
1301 }
1302
1303 #[test]
1304 fn test_sarkar_sibling_separation() {
1305 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1307
1308 let root_sig = network.add_node(
1309 NodeMetadata::new("/".to_string(), None),
1310 b"root".to_vec(),
1311 None,
1312 0
1313 ).unwrap();
1314
1315 let mut child_sigs = Vec::new();
1316 for i in 0..5 {
1317 let sig = network.add_node(
1318 NodeMetadata::new(format!("/child{}", i), None),
1319 format!("child{}", i).into_bytes(),
1320 Some(&root_sig),
1321 1
1322 ).unwrap();
1323 child_sigs.push(sig);
1324 }
1325
1326 let tau = constants::default_tau();
1328 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1329 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1330
1331 for sig in &child_sigs {
1332 let child_point = network.get_point(&sig.unique_id()).unwrap();
1333 let dist = root_point.hyperbolic_distance(&child_point);
1334 assert!((dist - tau).abs() < tolerance,
1335 "All children should be at distance τ from parent");
1336 }
1337
1338 for i in 0..child_sigs.len() {
1340 for j in (i+1)..child_sigs.len() {
1341 let pi = network.get_point(&child_sigs[i].unique_id()).unwrap();
1342 let pj = network.get_point(&child_sigs[j].unique_id()).unwrap();
1343 let dist = pi.hyperbolic_distance(&pj);
1344 assert!(dist > constants::epsilon(),
1345 "Siblings {} and {} should be at distinct positions", i, j);
1346 }
1347 }
1348 }
1349
1350 #[test]
1351 fn test_klein_points_created() {
1352 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1354
1355 let root_sig = network.add_node(
1356 NodeMetadata::new("/".to_string(), None),
1357 b"root".to_vec(),
1358 None,
1359 0
1360 ).unwrap();
1361
1362 let root_klein = network.get_klein_point(&root_sig.unique_id());
1363 assert!(root_klein.is_some(), "Root should have a Klein point");
1364
1365 let rk = root_klein.unwrap();
1366 assert!(rk.coords[0].abs() < constants::epsilon());
1368 assert!(rk.weight > constants::half());
1369
1370 let child_sig = network.add_node(
1371 NodeMetadata::new("/child".to_string(), None),
1372 b"child".to_vec(),
1373 Some(&root_sig),
1374 1
1375 ).unwrap();
1376
1377 let child_klein = network.get_klein_point(&child_sig.unique_id());
1378 assert!(child_klein.is_some(), "Child should have a Klein point");
1379 assert!(child_klein.unwrap().coords.length() > constants::epsilon(),
1380 "Child Klein point should be away from origin");
1381 }
1382
1383 #[test]
1384 fn test_power_cells_created() {
1385 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1386
1387 let root_sig = network.add_node(
1388 NodeMetadata::new("/".to_string(), None),
1389 b"root".to_vec(),
1390 None,
1391 0
1392 ).unwrap();
1393
1394 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1396 assert!(root_cell.half_planes.is_empty(), "Root cell should have no constraints initially");
1397
1398 let child_sig = network.add_node(
1399 NodeMetadata::new("/child".to_string(), None),
1400 b"child".to_vec(),
1401 Some(&root_sig),
1402 1
1403 ).unwrap();
1404
1405 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1407 assert_eq!(root_cell.half_planes.len(), 1, "Root should have 1 half-plane after adding child");
1408
1409 let child_cell = network.get_power_cell(&child_sig.unique_id()).unwrap();
1410 assert_eq!(child_cell.half_planes.len(), 1, "Child (leaf) should have 1 half-plane");
1411 }
1412
1413 #[test]
1414 fn test_nearest_neighbor_point_finds_self() {
1415 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1417
1418 let root_sig = network.add_node(
1419 NodeMetadata::new("/".to_string(), None),
1420 b"root".to_vec(),
1421 None,
1422 0
1423 ).unwrap();
1424
1425 let child_sig = network.add_node(
1426 NodeMetadata::new("/child".to_string(), None),
1427 b"child".to_vec(),
1428 Some(&root_sig),
1429 1
1430 ).unwrap();
1431
1432 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1434 let (nn_id, nn_dist) = network.nearest_neighbor_point(&child_point).unwrap();
1435
1436 assert_eq!(nn_id, child_sig.unique_id(),
1437 "Nearest neighbor at child's position should be child itself");
1438 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1439 assert!(nn_dist < tolerance,
1440 "Distance to self should be ~0, got {}", nn_dist);
1441 }
1442
1443 #[test]
1444 fn test_grid_reflects_insertions() {
1445 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1446
1447 let root_sig = network.add_node(
1448 NodeMetadata::new("/".to_string(), None),
1449 b"root".to_vec(),
1450 None,
1451 0
1452 ).unwrap();
1453
1454 assert!(network.grid_assigned_tile_count() > 0, "Grid should have tiles after root insert");
1456
1457 let _child_sig = network.add_node(
1458 NodeMetadata::new("/child".to_string(), None),
1459 b"child".to_vec(),
1460 Some(&root_sig),
1461 1
1462 ).unwrap();
1463
1464 assert!(network.grid_assigned_tile_count() > 0, "Grid should still have tiles after child insert");
1466 }
1467
1468 #[test]
1469 fn test_delete_cleans_up_klein_state() {
1470 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1471
1472 let root_sig = network.add_node(
1473 NodeMetadata::new("/".to_string(), None),
1474 b"root".to_vec(),
1475 None,
1476 0
1477 ).unwrap();
1478
1479 let child_sig = network.add_node(
1480 NodeMetadata::new("/child".to_string(), None),
1481 b"child".to_vec(),
1482 Some(&root_sig),
1483 1
1484 ).unwrap();
1485
1486 let child_id = child_sig.unique_id();
1487
1488 assert!(network.get_klein_point(&child_id).is_some());
1490 assert!(network.get_power_cell(&child_id).is_some());
1491
1492 network.unregister_node(&child_id);
1494
1495 assert!(network.get_klein_point(&child_id).is_none());
1497 assert!(network.get_power_cell(&child_id).is_none());
1498
1499 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1501 assert!(root_cell.half_planes.is_empty(),
1502 "Root cell should have no half-planes after child deletion");
1503 }
1504
1505 #[test]
1506 fn test_semantic_distance_identical() {
1507 let coords = {
1509 let mut v = vec![0u8; 3 * 16]; let val = FixedPoint::from_f64(0.5).raw().to_le_bytes();
1511 v[0..16].copy_from_slice(&val);
1512 v[16..32].copy_from_slice(&val);
1513 v[32..48].copy_from_slice(&val);
1514 v
1515 };
1516 let dist = HyperbolicTensorNetwork::semantic_distance(&coords, &coords, &(0..3));
1517 assert!(dist < constants::epsilon(), "Distance to self should be ~0, got {}", dist);
1518 }
1519
1520 #[test]
1521 fn test_semantic_distance_known_value() {
1522 let mut a = vec![0u8; 2 * 16];
1524 let one = FixedPoint::from_f64(1.0).raw().to_le_bytes();
1525 a[0..16].copy_from_slice(&one);
1526 let b = vec![0u8; 2 * 16]; let dist = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(0..2));
1531 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1532 assert!((dist - FixedPoint::from_int(1)).abs() < tolerance,
1533 "Distance should be 1.0, got {}", dist);
1534 }
1535
1536 #[test]
1537 fn test_semantic_distance_dimensional_slice() {
1538 let mut a = vec![0u8; 2 * 16];
1540 let one = FixedPoint::from_f64(1.0).raw().to_le_bytes();
1541 a[0..16].copy_from_slice(&one); let b = vec![0u8; 2 * 16]; let dist = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(1..2));
1547 assert!(dist < constants::epsilon(),
1548 "Slicing only dim 1 should give distance ~0, got {}", dist);
1549
1550 let dist_full = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(0..1));
1552 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1553 assert!((dist_full - FixedPoint::from_int(1)).abs() < tolerance,
1554 "Slicing dim 0 should give distance 1.0, got {}", dist_full);
1555 }
1556
1557 #[test]
1558 fn test_nearest_semantic_basic() {
1559 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1560
1561 let root_sig = network.add_node(
1563 NodeMetadata::new("/".to_string(), None),
1564 b"root".to_vec(), None, 0,
1565 ).unwrap();
1566
1567 let a_sig = network.add_node(
1568 NodeMetadata::new("/a".to_string(), None),
1569 b"a".to_vec(), Some(&root_sig), 1,
1570 ).unwrap();
1571
1572 let b_sig = network.add_node(
1573 NodeMetadata::new("/b".to_string(), None),
1574 b"b".to_vec(), Some(&root_sig), 1,
1575 ).unwrap();
1576
1577 let c_sig = network.add_node(
1578 NodeMetadata::new("/c".to_string(), None),
1579 b"c".to_vec(), Some(&root_sig), 1,
1580 ).unwrap();
1581
1582 let make_coords = |d0: f64, d1: f64| -> Vec<u8> {
1584 let mut v = vec![0u8; 2 * 16];
1585 v[0..16].copy_from_slice(&FixedPoint::from_f64(d0).raw().to_le_bytes());
1586 v[16..32].copy_from_slice(&FixedPoint::from_f64(d1).raw().to_le_bytes());
1587 v
1588 };
1589
1590 network.set_node_semantic(&a_sig.unique_id(), make_coords(0.8, 0.1));
1591 network.set_node_semantic(&b_sig.unique_id(), make_coords(0.7, 0.2));
1592 network.set_node_semantic(&c_sig.unique_id(), make_coords(0.1, 0.9));
1593
1594 let query = make_coords(0.8, 0.1);
1596 let results = network.nearest_semantic(&query, 3, &(0..2));
1597
1598 assert!(!results.is_empty());
1599
1600 let first_dist = results[0].1;
1602 assert!(first_dist < FixedPoint::from_f64(0.01),
1603 "Nearest to (0.8,0.1) should be /a at ~0 distance, got {}", first_dist);
1604
1605 if results.len() >= 3 {
1607 assert!(results[2].1 > results[1].1,
1608 "Third result should be farther than second");
1609 }
1610 }
1611}