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)> {
762 if self.klein_points.is_empty() {
763 return None;
764 }
765
766 const VERIFY_K: usize = 5;
768
769 let query_klein = klein::poincare_to_klein(query_poincare);
770
771 let mut candidates: Vec<String> = Vec::with_capacity(VERIFY_K + 2);
773
774 let grid_candidate = self.point_location.read().unwrap_or_else(|e| e.into_inner()).query(&query_klein.coords).map(|s| s.to_string());
775 if let Some(candidate_id) = grid_candidate {
776 match self.power_cells.get(&candidate_id).map(|r| r.value().clone()) {
777 Some(cell) if cell.half_planes.len() > VERIFY_K => {
778 let mut pd_ranked: Vec<(String, FixedPoint)> =
781 Vec::with_capacity(cell.half_planes.len() + 1);
782 if let Some(ck) = self.klein_points.get(&candidate_id) {
783 pd_ranked.push((
784 candidate_id.clone(),
785 klein::power_distance(&query_klein.coords, ck.value()),
786 ));
787 }
788 for hp in &cell.half_planes {
789 if let Some(nk) = self.klein_points.get(&hp.neighbor_id) {
790 pd_ranked.push((
791 hp.neighbor_id.clone(),
792 klein::power_distance(&query_klein.coords, nk.value()),
793 ));
794 }
795 }
796 pd_ranked.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
797 candidates.extend(pd_ranked.into_iter().take(VERIFY_K).map(|(id, _)| id));
798 }
799 Some(cell) => {
800 candidates.push(candidate_id);
801 candidates.extend(cell.half_planes.iter().map(|hp| hp.neighbor_id.clone()));
802 }
803 None => candidates.push(candidate_id),
804 }
805 }
806
807 candidates.extend(
811 self.hash_table
812 .find_nearest_nodes(query_poincare, 1)
813 .into_iter()
814 .map(|(id, _)| id),
815 );
816
817 let mut best: Option<(String, FixedPoint)> = None;
823 for id in candidates {
824 let Some(point) = self.point_map.get(&id) else { continue };
825 let ratio = query_poincare.hyperbolic_ratio(point.value());
826 if best.as_ref().map_or(true, |(_, incumbent)| ratio < *incumbent) {
827 best = Some((id, ratio));
828 }
829 }
830 best.map(|(id, ratio)| (id, ratio_to_distance(ratio)))
831 }
832
833 pub fn nearest_neighbor_point_k(&self, query_poincare: &HyperbolicPoint, k: usize) -> Vec<(String, FixedPoint)> {
838 if self.klein_points.is_empty() || k == 0 {
839 return Vec::new();
840 }
841
842 let query_klein = klein::poincare_to_klein(query_poincare);
843
844 let mut candidates: Vec<(String, FixedPoint)> = Vec::new();
846
847 let grid_candidate = self.point_location.read().unwrap_or_else(|e| e.into_inner())
848 .query(&query_klein.coords).map(|s| s.to_string());
849
850 if let Some(candidate_id) = grid_candidate.as_deref() {
851 if let Some(point) = self.point_map.get(candidate_id) {
853 let dist = query_poincare.hyperbolic_distance(point.value());
854 candidates.push((candidate_id.to_string(), dist));
855 }
856
857 if let Some(cell) = self.power_cells.get(candidate_id).map(|r| r.value().clone()) {
859 for hp in &cell.half_planes {
860 if let Some(point) = self.point_map.get(&hp.neighbor_id) {
861 let dist = query_poincare.hyperbolic_distance(point.value());
862 candidates.push((hp.neighbor_id.clone(), dist));
863 }
864 }
865 }
866 }
867
868 let vp_results = self.hash_table.find_nearest_nodes(query_poincare, k + candidates.len());
870 for (id, dist) in vp_results {
871 if !candidates.iter().any(|(cid, _)| cid == &id) {
872 candidates.push((id, dist));
873 }
874 }
875
876 candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
877 candidates.truncate(k);
878 candidates
879 }
880
881 pub fn get_klein_point(&self, unique_id: &str) -> Option<KleinPoint> {
883 self.klein_points.get(unique_id).map(|r| r.value().clone())
884 }
885
886 pub fn get_power_cell(&self, unique_id: &str) -> Option<PowerCell> {
888 self.power_cells.get(unique_id).map(|r| r.value().clone())
889 }
890
891 pub fn grid_assigned_tile_count(&self) -> usize {
893 self.point_location.read().unwrap_or_else(|e| e.into_inner()).assigned_tile_count()
894 }
895
896 pub fn semantic_distance(
911 coords_a: &[u8],
912 coords_b: &[u8],
913 dim_range: &Range<usize>,
914 ) -> FixedPoint {
915 let a = Self::decode_semantic_slice(coords_a, dim_range);
916 let b = Self::decode_semantic_slice(coords_b, dim_range);
917 g_math::fixed_point::imperative::fused::euclidean_distance(&a, &b)
918 }
919
920 pub fn decode_semantic_slice(coords: &[u8], dim_range: &Range<usize>) -> Vec<FixedPoint> {
925 dim_range
926 .clone()
927 .map(|dim| {
928 let start = dim * 16;
929 let end = start + 16;
930 if coords.len() >= end {
931 FixedPoint::from_raw(i128::from_le_bytes(
932 coords[start..end].try_into().unwrap(),
933 ))
934 } else {
935 FixedPoint::from_int(0)
936 }
937 })
938 .collect()
939 }
940
941 pub fn nearest_semantic(
959 &self,
960 query_coords: &[u8],
961 k: usize,
962 dim_range: &Range<usize>,
963 ) -> Vec<(String, FixedPoint)> {
964 if k == 0 {
965 return Vec::new();
966 }
967
968 if self.nodes.len() < constants::SEMANTIC_INDEX_MIN_NODES {
969 return self.nearest_semantic_scan(query_coords, k, dim_range);
970 }
971
972 let query = Self::decode_semantic_slice(query_coords, dim_range);
973 let index = self.semantic_index.get_or_build(dim_range, || {
974 self.nodes
975 .iter()
976 .filter(|entry| !entry.value().semantic_coords().is_empty())
977 .map(|entry| {
978 (
979 entry.value().metadata().key.clone(),
980 Self::decode_semantic_slice(entry.value().semantic_coords(), dim_range),
981 )
982 })
983 .collect()
984 });
985 index.tree.knn(&query, k, &EuclideanMetric)
986 }
987
988 pub fn nearest_semantic_scan(
995 &self,
996 query_coords: &[u8],
997 k: usize,
998 dim_range: &Range<usize>,
999 ) -> Vec<(String, FixedPoint)> {
1000 if k == 0 {
1001 return Vec::new();
1002 }
1003
1004 let mut heap: BinaryHeap<(FixedPoint, String)> = BinaryHeap::new();
1008
1009 for entry in self.nodes.iter() {
1010 let coords = entry.value().semantic_coords();
1011
1012 if coords.is_empty() {
1014 continue;
1015 }
1016
1017 let dist = Self::semantic_distance(query_coords, coords, dim_range);
1018 let key = entry.value().metadata().key.as_str();
1019
1020 if heap.len() < k {
1021 heap.push((dist, key.to_string()));
1022 } else if let Some(worst) = heap.peek() {
1023 if (dist, key) < (worst.0, worst.1.as_str()) {
1024 heap.pop();
1025 heap.push((dist, key.to_string()));
1026 }
1027 }
1028 }
1029
1030 let mut results: Vec<(String, FixedPoint)> = heap
1032 .into_iter()
1033 .map(|(dist, uid)| (uid, dist))
1034 .collect();
1035 results.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
1036 results
1037 }
1038
1039 pub fn validate_network(&self) -> bool {
1044 if self.nodes.is_empty() {
1045 return false;
1046 }
1047
1048 let root_sig = self.root_signature.lock().unwrap_or_else(|e| e.into_inner());
1049 if root_sig.is_none() {
1050 return false;
1051 }
1052
1053 let root_id = root_sig.as_ref().unwrap().unique_id();
1054 drop(root_sig);
1055 if !self.nodes.contains_key(&root_id) {
1056 return false;
1057 }
1058
1059 for entry in self.nodes.iter() {
1061 let node = entry.value();
1062 for child_sig in node.children() {
1063 if !self.nodes.contains_key(&child_sig.unique_id()) {
1064 return false;
1065 }
1066 }
1067 }
1068
1069 for entry in self.nodes.iter() {
1071 if !self.point_map.contains_key(entry.key()) {
1072 return false;
1073 }
1074 }
1075
1076 for entry in self.point_map.iter() {
1078 if !self.nodes.contains_key(entry.key()) {
1079 return false;
1080 }
1081 }
1082
1083 for entry in self.klein_points.iter() {
1085 if !self.nodes.contains_key(entry.key()) {
1086 return false;
1087 }
1088 }
1089
1090 for entry in self.power_cells.iter() {
1092 if !self.nodes.contains_key(entry.key()) {
1093 return false;
1094 }
1095 }
1096
1097 for entry in self.power_cells.iter() {
1099 for hp in &entry.value().half_planes {
1100 if !self.nodes.contains_key(&hp.neighbor_id) {
1101 return false;
1102 }
1103 }
1104 }
1105
1106 for entry in self.child_counts.iter() {
1108 if !self.nodes.contains_key(entry.key()) {
1109 return false;
1110 }
1111 }
1112
1113 true
1114 }
1115}
1116
1117impl Debug for HyperbolicTensorNetwork {
1118 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1119 write!(f, "HyperbolicTensorNetwork(nodes={}, dimension={})",
1120 self.nodes.len(),
1121 self.hash_table.poincare_disk().dimension())
1122 }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use super::*;
1128
1129 #[test]
1130 fn test_compressed_node() {
1131 let metadata = NodeMetadata::new("test".to_string(), None);
1132 let value = b"Node data".to_vec();
1133
1134 let node = CompressedNode::new(metadata, value.clone());
1135
1136 assert_eq!(node.metadata().key, "test");
1137 assert_eq!(node.value(), &value[..]);
1138 assert!(!node.has_children());
1139 assert_eq!(node.child_count(), 0);
1140 }
1141
1142 #[test]
1143 fn test_tensor_network_creation() {
1144 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1145
1146 assert_eq!(network.node_count(), 0);
1147 assert!(network.root_node().is_none());
1148 }
1149
1150 #[test]
1151 fn test_adding_nodes() {
1152 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1153
1154 let root_meta = NodeMetadata::new("/".to_string(), None);
1155 let root_sig = network.add_node(
1156 root_meta,
1157 b"Root node data".to_vec(),
1158 None,
1159 0
1160 ).unwrap();
1161
1162 assert_eq!(network.node_count(), 1);
1163 assert!(network.root_node().is_some());
1164
1165 let child_meta = NodeMetadata::new("/child".to_string(), None);
1166 let child_sig = network.add_node(
1167 child_meta,
1168 b"Child node data".to_vec(),
1169 Some(&root_sig),
1170 1
1171 ).unwrap();
1172
1173 assert_eq!(network.node_count(), 2);
1174
1175 let root_node = network.get_node_by_signature(&root_sig).unwrap();
1176 assert_eq!(root_node.child_count(), 1);
1177 assert_eq!(root_node.children()[0].unique_id(), child_sig.unique_id());
1178 }
1179
1180 #[test]
1181 fn test_network_validation() {
1182 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1183
1184 assert!(!network.validate_network());
1185
1186 let root_sig = network.add_node(
1187 NodeMetadata::new("/".to_string(), None),
1188 b"Root data".to_vec(),
1189 None,
1190 0
1191 ).unwrap();
1192
1193 assert!(network.validate_network());
1194
1195 network.add_node(
1196 NodeMetadata::new("/child1".to_string(), None),
1197 b"Child 1 data".to_vec(),
1198 Some(&root_sig),
1199 1
1200 ).unwrap();
1201
1202 network.add_node(
1203 NodeMetadata::new("/child2".to_string(), None),
1204 b"Child 2 data".to_vec(),
1205 Some(&root_sig),
1206 1
1207 ).unwrap();
1208
1209 assert!(network.validate_network());
1210 }
1211
1212 #[test]
1213 fn test_point_map() {
1214 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1215
1216 let root_sig = network.add_node(
1217 NodeMetadata::new("/".to_string(), None),
1218 b"root".to_vec(),
1219 None,
1220 0
1221 ).unwrap();
1222
1223 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1225 assert!(root_point.euclidean_norm() < constants::epsilon());
1226
1227 let child_sig = network.add_node(
1228 NodeMetadata::new("/child".to_string(), None),
1229 b"child".to_vec(),
1230 Some(&root_sig),
1231 1
1232 ).unwrap();
1233
1234 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1236 assert!(child_point.euclidean_norm() > constants::epsilon());
1237 }
1238
1239 #[test]
1240 fn test_spatial_descendants() {
1241 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1242
1243 let root_sig = network.add_node(
1244 NodeMetadata::new("/".to_string(), None),
1245 b"root".to_vec(),
1246 None,
1247 0
1248 ).unwrap();
1249
1250 let child_sig = network.add_node(
1251 NodeMetadata::new("/child".to_string(), None),
1252 b"child".to_vec(),
1253 Some(&root_sig),
1254 1
1255 ).unwrap();
1256
1257 let _grandchild_sig = network.add_node(
1258 NodeMetadata::new("/child/grandchild".to_string(), None),
1259 b"grandchild".to_vec(),
1260 Some(&child_sig),
1261 2
1262 ).unwrap();
1263
1264 let descendants = network.find_descendants_spatial(&root_sig);
1266 assert!(descendants.len() >= 2,
1267 "Expected at least 2 descendants, got {}", descendants.len());
1268 }
1269
1270 #[test]
1271 fn test_sarkar_child_distance() {
1272 let tau = constants::default_tau();
1274 let network = HyperbolicTensorNetwork::new(2, tau);
1275
1276 let root_sig = network.add_node(
1277 NodeMetadata::new("/".to_string(), None),
1278 b"root".to_vec(),
1279 None,
1280 0
1281 ).unwrap();
1282
1283 let child_sig = network.add_node(
1284 NodeMetadata::new("/child".to_string(), None),
1285 b"child".to_vec(),
1286 Some(&root_sig),
1287 1
1288 ).unwrap();
1289
1290 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1291 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1292
1293 let dist = root_point.hyperbolic_distance(&child_point);
1294 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1295 assert!((dist - tau).abs() < tolerance,
1296 "Child should be at distance τ={} from parent, got {}", tau, dist);
1297 }
1298
1299 #[test]
1300 fn test_sarkar_sibling_separation() {
1301 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1303
1304 let root_sig = network.add_node(
1305 NodeMetadata::new("/".to_string(), None),
1306 b"root".to_vec(),
1307 None,
1308 0
1309 ).unwrap();
1310
1311 let mut child_sigs = Vec::new();
1312 for i in 0..5 {
1313 let sig = network.add_node(
1314 NodeMetadata::new(format!("/child{}", i), None),
1315 format!("child{}", i).into_bytes(),
1316 Some(&root_sig),
1317 1
1318 ).unwrap();
1319 child_sigs.push(sig);
1320 }
1321
1322 let tau = constants::default_tau();
1324 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1325 let root_point = network.get_point(&root_sig.unique_id()).unwrap();
1326
1327 for sig in &child_sigs {
1328 let child_point = network.get_point(&sig.unique_id()).unwrap();
1329 let dist = root_point.hyperbolic_distance(&child_point);
1330 assert!((dist - tau).abs() < tolerance,
1331 "All children should be at distance τ from parent");
1332 }
1333
1334 for i in 0..child_sigs.len() {
1336 for j in (i+1)..child_sigs.len() {
1337 let pi = network.get_point(&child_sigs[i].unique_id()).unwrap();
1338 let pj = network.get_point(&child_sigs[j].unique_id()).unwrap();
1339 let dist = pi.hyperbolic_distance(&pj);
1340 assert!(dist > constants::epsilon(),
1341 "Siblings {} and {} should be at distinct positions", i, j);
1342 }
1343 }
1344 }
1345
1346 #[test]
1347 fn test_klein_points_created() {
1348 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1350
1351 let root_sig = network.add_node(
1352 NodeMetadata::new("/".to_string(), None),
1353 b"root".to_vec(),
1354 None,
1355 0
1356 ).unwrap();
1357
1358 let root_klein = network.get_klein_point(&root_sig.unique_id());
1359 assert!(root_klein.is_some(), "Root should have a Klein point");
1360
1361 let rk = root_klein.unwrap();
1362 assert!(rk.coords[0].abs() < constants::epsilon());
1364 assert!(rk.weight > constants::half());
1365
1366 let child_sig = network.add_node(
1367 NodeMetadata::new("/child".to_string(), None),
1368 b"child".to_vec(),
1369 Some(&root_sig),
1370 1
1371 ).unwrap();
1372
1373 let child_klein = network.get_klein_point(&child_sig.unique_id());
1374 assert!(child_klein.is_some(), "Child should have a Klein point");
1375 assert!(child_klein.unwrap().coords.length() > constants::epsilon(),
1376 "Child Klein point should be away from origin");
1377 }
1378
1379 #[test]
1380 fn test_power_cells_created() {
1381 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1382
1383 let root_sig = network.add_node(
1384 NodeMetadata::new("/".to_string(), None),
1385 b"root".to_vec(),
1386 None,
1387 0
1388 ).unwrap();
1389
1390 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1392 assert!(root_cell.half_planes.is_empty(), "Root cell should have no constraints initially");
1393
1394 let child_sig = network.add_node(
1395 NodeMetadata::new("/child".to_string(), None),
1396 b"child".to_vec(),
1397 Some(&root_sig),
1398 1
1399 ).unwrap();
1400
1401 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1403 assert_eq!(root_cell.half_planes.len(), 1, "Root should have 1 half-plane after adding child");
1404
1405 let child_cell = network.get_power_cell(&child_sig.unique_id()).unwrap();
1406 assert_eq!(child_cell.half_planes.len(), 1, "Child (leaf) should have 1 half-plane");
1407 }
1408
1409 #[test]
1410 fn test_nearest_neighbor_point_finds_self() {
1411 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1413
1414 let root_sig = network.add_node(
1415 NodeMetadata::new("/".to_string(), None),
1416 b"root".to_vec(),
1417 None,
1418 0
1419 ).unwrap();
1420
1421 let child_sig = network.add_node(
1422 NodeMetadata::new("/child".to_string(), None),
1423 b"child".to_vec(),
1424 Some(&root_sig),
1425 1
1426 ).unwrap();
1427
1428 let child_point = network.get_point(&child_sig.unique_id()).unwrap();
1430 let (nn_id, nn_dist) = network.nearest_neighbor_point(&child_point).unwrap();
1431
1432 assert_eq!(nn_id, child_sig.unique_id(),
1433 "Nearest neighbor at child's position should be child itself");
1434 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1435 assert!(nn_dist < tolerance,
1436 "Distance to self should be ~0, got {}", nn_dist);
1437 }
1438
1439 #[test]
1440 fn test_grid_reflects_insertions() {
1441 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1442
1443 let root_sig = network.add_node(
1444 NodeMetadata::new("/".to_string(), None),
1445 b"root".to_vec(),
1446 None,
1447 0
1448 ).unwrap();
1449
1450 assert!(network.grid_assigned_tile_count() > 0, "Grid should have tiles after root insert");
1452
1453 let _child_sig = network.add_node(
1454 NodeMetadata::new("/child".to_string(), None),
1455 b"child".to_vec(),
1456 Some(&root_sig),
1457 1
1458 ).unwrap();
1459
1460 assert!(network.grid_assigned_tile_count() > 0, "Grid should still have tiles after child insert");
1462 }
1463
1464 #[test]
1465 fn test_delete_cleans_up_klein_state() {
1466 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1467
1468 let root_sig = network.add_node(
1469 NodeMetadata::new("/".to_string(), None),
1470 b"root".to_vec(),
1471 None,
1472 0
1473 ).unwrap();
1474
1475 let child_sig = network.add_node(
1476 NodeMetadata::new("/child".to_string(), None),
1477 b"child".to_vec(),
1478 Some(&root_sig),
1479 1
1480 ).unwrap();
1481
1482 let child_id = child_sig.unique_id();
1483
1484 assert!(network.get_klein_point(&child_id).is_some());
1486 assert!(network.get_power_cell(&child_id).is_some());
1487
1488 network.unregister_node(&child_id);
1490
1491 assert!(network.get_klein_point(&child_id).is_none());
1493 assert!(network.get_power_cell(&child_id).is_none());
1494
1495 let root_cell = network.get_power_cell(&root_sig.unique_id()).unwrap();
1497 assert!(root_cell.half_planes.is_empty(),
1498 "Root cell should have no half-planes after child deletion");
1499 }
1500
1501 #[test]
1502 fn test_semantic_distance_identical() {
1503 let coords = {
1505 let mut v = vec![0u8; 3 * 16]; let val = FixedPoint::from_f64(0.5).raw().to_le_bytes();
1507 v[0..16].copy_from_slice(&val);
1508 v[16..32].copy_from_slice(&val);
1509 v[32..48].copy_from_slice(&val);
1510 v
1511 };
1512 let dist = HyperbolicTensorNetwork::semantic_distance(&coords, &coords, &(0..3));
1513 assert!(dist < constants::epsilon(), "Distance to self should be ~0, got {}", dist);
1514 }
1515
1516 #[test]
1517 fn test_semantic_distance_known_value() {
1518 let mut a = vec![0u8; 2 * 16];
1520 let one = FixedPoint::from_f64(1.0).raw().to_le_bytes();
1521 a[0..16].copy_from_slice(&one);
1522 let b = vec![0u8; 2 * 16]; let dist = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(0..2));
1527 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1528 assert!((dist - FixedPoint::from_int(1)).abs() < tolerance,
1529 "Distance should be 1.0, got {}", dist);
1530 }
1531
1532 #[test]
1533 fn test_semantic_distance_dimensional_slice() {
1534 let mut a = vec![0u8; 2 * 16];
1536 let one = FixedPoint::from_f64(1.0).raw().to_le_bytes();
1537 a[0..16].copy_from_slice(&one); let b = vec![0u8; 2 * 16]; let dist = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(1..2));
1543 assert!(dist < constants::epsilon(),
1544 "Slicing only dim 1 should give distance ~0, got {}", dist);
1545
1546 let dist_full = HyperbolicTensorNetwork::semantic_distance(&a, &b, &(0..1));
1548 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(100);
1549 assert!((dist_full - FixedPoint::from_int(1)).abs() < tolerance,
1550 "Slicing dim 0 should give distance 1.0, got {}", dist_full);
1551 }
1552
1553 #[test]
1554 fn test_nearest_semantic_basic() {
1555 let network = HyperbolicTensorNetwork::new(2, constants::default_tau());
1556
1557 let root_sig = network.add_node(
1559 NodeMetadata::new("/".to_string(), None),
1560 b"root".to_vec(), None, 0,
1561 ).unwrap();
1562
1563 let a_sig = network.add_node(
1564 NodeMetadata::new("/a".to_string(), None),
1565 b"a".to_vec(), Some(&root_sig), 1,
1566 ).unwrap();
1567
1568 let b_sig = network.add_node(
1569 NodeMetadata::new("/b".to_string(), None),
1570 b"b".to_vec(), Some(&root_sig), 1,
1571 ).unwrap();
1572
1573 let c_sig = network.add_node(
1574 NodeMetadata::new("/c".to_string(), None),
1575 b"c".to_vec(), Some(&root_sig), 1,
1576 ).unwrap();
1577
1578 let make_coords = |d0: f64, d1: f64| -> Vec<u8> {
1580 let mut v = vec![0u8; 2 * 16];
1581 v[0..16].copy_from_slice(&FixedPoint::from_f64(d0).raw().to_le_bytes());
1582 v[16..32].copy_from_slice(&FixedPoint::from_f64(d1).raw().to_le_bytes());
1583 v
1584 };
1585
1586 network.set_node_semantic(&a_sig.unique_id(), make_coords(0.8, 0.1));
1587 network.set_node_semantic(&b_sig.unique_id(), make_coords(0.7, 0.2));
1588 network.set_node_semantic(&c_sig.unique_id(), make_coords(0.1, 0.9));
1589
1590 let query = make_coords(0.8, 0.1);
1592 let results = network.nearest_semantic(&query, 3, &(0..2));
1593
1594 assert!(!results.is_empty());
1595
1596 let first_dist = results[0].1;
1598 assert!(first_dist < FixedPoint::from_f64(0.01),
1599 "Nearest to (0.8,0.1) should be /a at ~0 distance, got {}", first_dist);
1600
1601 if results.len() >= 3 {
1603 assert!(results[2].1 > results[1].1,
1604 "Third result should be farther than second");
1605 }
1606 }
1607}