Skip to main content

horon_engine/
tree_tensor.rs

1//! tree_tensor.rs - Hyperbolic Tree Tensor Core Implementation
2//!
3//! Efficient hierarchical data representation combining hyperbolic geometry
4//! and geometric hashing: path lookups are hash-map access, spatial queries
5//! go through the bucketed VP-tree index (see per-method docs for costs).
6
7use std::collections::HashMap;
8use std::fmt::{self, Debug, Formatter};
9use std::ops::Range;
10use std::sync::Arc;
11use dashmap::DashMap;
12use super::concurrency::StripedLock;
13use super::hash_table::GeometricSignature;
14use super::tensor_network::{HyperbolicTensorNetwork, CompressedNode, NodeMetadata};
15
16/// Error type for tree tensor integration operations.
17#[derive(Debug)]
18pub enum IntegrationError {
19    /// A node already exists at the target path.
20    AlreadyExists(String),
21    /// No node exists at the target path.
22    NotFound(String),
23    /// The operation could not be completed.
24    OperationFailed(String),
25    /// Input failed validation (dimensions, structure, or constraints).
26    ValidationFailed(String),
27    /// Stored bytes could not be deserialized.
28    DeserializationError(String),
29    /// A lock could not be acquired.
30    LockError(String),
31    /// The supplied configuration is invalid.
32    ConfigurationError(String),
33}
34
35impl IntegrationError {
36    /// Create a configuration error.
37    pub fn configuration_error(msg: String) -> Self {
38        Self::ConfigurationError(msg)
39    }
40}
41
42impl std::fmt::Display for IntegrationError {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::AlreadyExists(s) => write!(f, "already exists: {s}"),
46            Self::NotFound(s) => write!(f, "not found: {s}"),
47            Self::OperationFailed(s) => write!(f, "operation failed: {s}"),
48            Self::ValidationFailed(s) => write!(f, "validation failed: {s}"),
49            Self::DeserializationError(s) => write!(f, "deserialization error: {s}"),
50            Self::LockError(s) => write!(f, "lock error: {s}"),
51            Self::ConfigurationError(s) => write!(f, "configuration error: {s}"),
52        }
53    }
54}
55
56impl std::error::Error for IntegrationError {}
57
58/// Result type alias for tree-tensor integration operations.
59pub type IntegrationResult<T> = Result<T, IntegrationError>;
60
61/// Path operations for hierarchical data.
62pub trait PathOperations {
63    /// Split a path into components.
64    fn split_path(&self, path: &str) -> Vec<String>;
65
66    /// Join path components into a full path.
67    fn join_path(&self, components: &[String]) -> String;
68
69    /// Get parent path from a path.
70    fn parent_path(&self, path: &str) -> Option<String>;
71
72    /// Get the last component of a path.
73    fn last_component(&self, path: &str) -> Option<String>;
74}
75
76/// Default path operations implementation using '/' separator.
77pub struct DefaultPathOps;
78
79impl PathOperations for DefaultPathOps {
80    fn split_path(&self, path: &str) -> Vec<String> {
81        path.split('/')
82            .filter(|s| !s.is_empty())
83            .map(String::from)
84            .collect()
85    }
86
87    fn join_path(&self, components: &[String]) -> String {
88        let mut path = String::new();
89        for component in components {
90            path.push('/');
91            path.push_str(component);
92        }
93        if path.is_empty() {
94            path.push('/');
95        }
96        path
97    }
98
99    fn parent_path(&self, path: &str) -> Option<String> {
100        if path == "/" {
101            return None;
102        }
103
104        let components = self.split_path(path);
105        if components.is_empty() {
106            return Some("/".to_string());
107        }
108
109        let parent_components = &components[0..components.len() - 1];
110        Some(self.join_path(parent_components))
111    }
112
113    fn last_component(&self, path: &str) -> Option<String> {
114        let components = self.split_path(path);
115        components.last().cloned()
116    }
117}
118
119/// Configuration for the Hyperbolic Tree Tensor.
120#[derive(Clone, Debug)]
121pub struct HTTConfig {
122    /// Dimension of the hyperbolic space
123    dimension: usize,
124    /// Maximum in-memory nodes before flushing to storage
125    max_memory_nodes: usize,
126    /// Cache size for frequently accessed nodes
127    cache_size: usize,
128    /// Sarkar embedding scale factor τ: parent-child hyperbolic distance
129    tau: g_math::fixed_point::FixedPoint,
130    /// Point location grid resolution (0 = use default of 64)
131    grid_resolution: usize,
132}
133
134impl HTTConfig {
135    /// Create a new HTT configuration (uses default τ = 1.0).
136    pub fn new(dimension: usize, max_memory_nodes: usize, cache_size: usize) -> Self {
137        Self {
138            dimension,
139            max_memory_nodes,
140            cache_size,
141            tau: crate::constants::default_tau(),
142            grid_resolution: 0,
143        }
144    }
145
146    /// Create a default configuration.
147    pub fn default_config() -> Self {
148        Self {
149            dimension: 4,
150            max_memory_nodes: 1000,
151            cache_size: 100,
152            tau: crate::constants::default_tau(),
153            grid_resolution: 0,
154        }
155    }
156
157    /// Set the Sarkar embedding scale factor τ.
158    pub fn with_tau(mut self, tau: g_math::fixed_point::FixedPoint) -> Self {
159        self.tau = tau;
160        self
161    }
162
163    /// Set the point location grid resolution.
164    pub fn with_grid_resolution(mut self, resolution: usize) -> Self {
165        self.grid_resolution = resolution;
166        self
167    }
168
169    /// Get the grid resolution (0 = use default).
170    pub fn grid_resolution(&self) -> usize {
171        self.grid_resolution
172    }
173
174    /// Get the dimension.
175    pub fn dimension(&self) -> usize {
176        self.dimension
177    }
178
179    /// Get the maximum memory nodes.
180    pub fn max_memory_nodes(&self) -> usize {
181        self.max_memory_nodes
182    }
183
184    /// Get the cache size.
185    pub fn cache_size(&self) -> usize {
186        self.cache_size
187    }
188
189    /// Get the Sarkar embedding scale factor τ.
190    pub fn tau(&self) -> g_math::fixed_point::FixedPoint {
191        self.tau
192    }
193}
194
195impl Default for HTTConfig {
196    fn default() -> Self {
197        Self::default_config()
198    }
199}
200
201/// Hyperbolic Tree Tensor - core data structure.
202///
203/// Combines hyperbolic geometry, tensor networks, and geometric hashing:
204/// path access is hash-map cost, spatial queries use the bucketed VP-tree
205/// index. Nodes are embedded in the Poincare disk with spatial indexing
206/// for geometric queries.
207///
208/// Path maps use DashMap for lock-free concurrent reads.
209pub struct HyperbolicTreeTensor {
210    /// Tensor network for spatial embedding
211    tensor_network: HyperbolicTensorNetwork,
212    /// Path operations
213    path_ops: Box<dyn PathOperations + Send + Sync>,
214    /// Signature map for O(1) path lookups: path -> signature
215    path_map: DashMap<String, GeometricSignature>,
216    /// Reverse map: unique_id -> path (for spatial query results)
217    id_to_path: DashMap<String, String>,
218    /// Striped parent locks: serialize writes to the same parent,
219    /// allow parallel writes to different parents (64 stripes)
220    parent_locks: StripedLock<64>,
221    /// Configuration
222    config: HTTConfig,
223}
224
225impl HyperbolicTreeTensor {
226    /// Create a new Hyperbolic Tree Tensor.
227    pub fn new(config: HTTConfig) -> Self {
228        let grid_res = config.grid_resolution();
229        let tensor_network = if grid_res > 0 {
230            HyperbolicTensorNetwork::with_grid_resolution(config.dimension(), config.tau(), grid_res)
231        } else {
232            HyperbolicTensorNetwork::new(config.dimension(), config.tau())
233        };
234
235        Self {
236            tensor_network,
237            path_ops: Box::new(DefaultPathOps),
238            path_map: DashMap::new(),
239            id_to_path: DashMap::new(),
240            parent_locks: StripedLock::new(),
241            config,
242        }
243    }
244
245    /// Resolve a node's parent signature, enforcing the tree invariant.
246    ///
247    /// Returns `Ok(None)` for a root insert (parent path is `None`, i.e. the
248    /// path is `/`), `Ok(Some(sig))` when the parent exists, and
249    /// `Err(NotFound)` when a non-root node's parent is absent. The last case
250    /// is the important one: `add_node` treats a `None` parent as a root and
251    /// rebuilds the point-location grid, so silently passing `None` for a
252    /// missing parent would wipe the spatial index.
253    fn resolve_parent(
254        &self,
255        path: &str,
256        parent_path: &Option<String>,
257    ) -> IntegrationResult<Option<GeometricSignature>> {
258        match parent_path {
259            Some(p) if p.as_str() != path => match self.path_map.get(p.as_str()) {
260                Some(r) => Ok(Some(r.value().clone())),
261                None => Err(IntegrationError::NotFound(format!(
262                    "cannot insert '{}': parent '{}' does not exist",
263                    path, p
264                ))),
265            },
266            _ => Ok(None), // root, or a degenerate self-parent
267        }
268    }
269
270    /// Insert a node without geometric embedding (data + semantic only).
271    ///
272    /// Much faster than `insert()` — skips Sarkar embedding, VP-tree, power
273    /// diagram, and point location grid. Use for bulk loading when spatial
274    /// queries are not needed (semantic queries still work).
275    pub fn insert_data_only(&self, path: &str, value: Vec<u8>, content_type: Option<String>) -> IntegrationResult<()> {
276        // Fast path: lock-free duplicate check.
277        if self.path_map.contains_key(path) {
278            return Err(IntegrationError::AlreadyExists(
279                format!("Node at path {} already exists", path),
280            ));
281        }
282
283        // Enforce the tree invariant: a non-root node's parent must exist.
284        // (HTTStorage creates ancestors first; a direct caller inserting an
285        // orphan would otherwise leave a dangling path.)
286        let parent_path = self.path_ops.parent_path(path);
287        if let Some(p) = &parent_path {
288            if p.as_str() != path && !self.path_map.contains_key(p.as_str()) {
289                return Err(IntegrationError::NotFound(format!(
290                    "cannot insert '{}': parent '{}' does not exist",
291                    path, p
292                )));
293            }
294        }
295
296        // Serialize sibling creation under the same parent (data-only nodes
297        // don't touch the geometric child list, but the stripe lock closes
298        // the TOCTOU between the duplicate check and the insert). Root inserts
299        // ("/") have no parent → no lock needed.
300        let _stripe_guard = parent_path.as_deref().map(|p| self.parent_locks.lock(p));
301
302        // Re-check under the lock.
303        if self.path_map.contains_key(path) {
304            return Err(IntegrationError::AlreadyExists(
305                format!("Node at path {} already exists", path),
306            ));
307        }
308
309        let metadata = NodeMetadata::new(path.to_string(), content_type);
310
311        let unique_id = self.tensor_network.add_node_data_only(metadata, value, 0);
312
313        // Create a stub signature for path_map (no geometric meaning)
314        let stub_sig = GeometricSignature::stub(&unique_id);
315        self.id_to_path.insert(unique_id, path.to_string());
316        self.path_map.insert(path.to_string(), stub_sig);
317
318        Ok(())
319    }
320
321    /// Insert a node at the specified path.
322    ///
323    /// Acquires a striped parent lock to serialize writes to the same parent
324    /// while allowing parallel writes to different parents.
325    pub fn insert(&self, path: &str, value: Vec<u8>, content_type: Option<String>) -> IntegrationResult<()> {
326        // Fast path: lock-free DashMap read
327        if self.path_map.contains_key(path) {
328            return Err(IntegrationError::AlreadyExists(
329                format!("Node at path {} already exists", path),
330            ));
331        }
332
333        let parent_path = self.path_ops.parent_path(path);
334        let level = self.path_depth(path);
335        let metadata = NodeMetadata::new(path.to_string(), content_type);
336
337        // Look up the parent signature (lock-free DashMap read). A non-root
338        // node whose parent is absent is rejected — silently treating it as a
339        // second root would wipe the point-location grid.
340        let parent_signature = self.resolve_parent(path, &parent_path)?;
341
342        // Acquire stripe lock on parent to serialize sibling creation.
343        // This ensures child_counts consistency and prevents duplicate path creation
344        // (TOCTOU between contains_key above and path_map.insert below).
345        // Different parents hit different stripes → parallel writes to independent subtrees.
346        let parent_uid = parent_signature.as_ref().map(|s| s.unique_id());
347        let _stripe_guard = parent_uid.as_deref().map(|uid| self.parent_locks.lock(uid));
348
349        // Re-check under the stripe lock (double-checked locking)
350        if self.path_map.contains_key(path) {
351            return Err(IntegrationError::AlreadyExists(
352                format!("Node at path {} already exists", path),
353            ));
354        }
355
356        let signature = self
357            .tensor_network
358            .add_node(metadata, value, parent_signature.as_ref(), level)
359            .ok_or_else(|| {
360                IntegrationError::OperationFailed(
361                    "Failed to add node to tensor network".to_string(),
362                )
363            })?;
364
365        let unique_id = signature.unique_id();
366        self.id_to_path.insert(unique_id, path.to_string());
367        self.path_map.insert(path.to_string(), signature);
368
369        // _stripe_guard dropped here, releasing the parent stripe lock
370        Ok(())
371    }
372
373    /// Insert a node with an explicit child_index for deterministic Sarkar reconstruction.
374    ///
375    /// Same as `insert()` but passes the child_index through to the tensor network
376    /// so the node gets the same geometric position regardless of insertion order.
377    pub fn insert_positioned(&self, path: &str, value: Vec<u8>, content_type: Option<String>, child_index: u32) -> IntegrationResult<()> {
378        if self.path_map.contains_key(path) {
379            return Err(IntegrationError::AlreadyExists(
380                format!("Node at path {} already exists", path),
381            ));
382        }
383
384        let parent_path = self.path_ops.parent_path(path);
385        let level = self.path_depth(path);
386        let metadata = NodeMetadata::new(path.to_string(), content_type);
387
388        let parent_signature = self.resolve_parent(path, &parent_path)?;
389
390        let parent_uid = parent_signature.as_ref().map(|s| s.unique_id());
391        let _stripe_guard = parent_uid.as_deref().map(|uid| self.parent_locks.lock(uid));
392
393        if self.path_map.contains_key(path) {
394            return Err(IntegrationError::AlreadyExists(
395                format!("Node at path {} already exists", path),
396            ));
397        }
398
399        let signature = self
400            .tensor_network
401            .add_node_positioned(metadata, value, parent_signature.as_ref(), level, child_index)
402            .ok_or_else(|| {
403                IntegrationError::OperationFailed(
404                    "Failed to add node to tensor network".to_string(),
405                )
406            })?;
407
408        let unique_id = signature.unique_id();
409        self.id_to_path.insert(unique_id, path.to_string());
410        self.path_map.insert(path.to_string(), signature);
411
412        Ok(())
413    }
414
415    /// Upgrade a data-only node to a full geometric embedding, in place.
416    ///
417    /// Data-only nodes (`insert_data_only`) live under a stub signature with
418    /// no Poincaré position — semantic queries see them, spatial queries do
419    /// not. This computes their Sarkar placement on demand: missing ancestors
420    /// are embedded first (placement needs an embedded parent; recursion is
421    /// bounded by the ~44/τ depth budget), then the node is re-registered
422    /// under its position-derived signature with its identity preserved —
423    /// key, value, user metadata, timestamps, and semantic coordinates all
424    /// carry over.
425    ///
426    /// Returns `Ok(true)` when this call performed the upgrade, `Ok(false)`
427    /// when the node was already embedded (idempotent), `NotFound` for
428    /// missing paths.
429    ///
430    /// Concurrency: takes the same parent stripe lock as `insert`, so embeds
431    /// serialize with sibling inserts and deletes; racing embeds of the same
432    /// node resolve to one upgrade (double-checked under the lock). Readers
433    /// never observe the node missing: the embedded replacement (with
434    /// coordinates already copied) goes live in `path_map` before the
435    /// data-only entry is retired — a concurrent semantic query may
436    /// transiently see the key twice (same key, same coordinates) inside
437    /// that window.
438    ///
439    /// Geometric positions are derived state and are NOT persisted: the
440    /// position depends on the sibling order at embed time, so it is
441    /// deterministic for a fixed operation sequence but not stable across
442    /// sessions. Callers re-embed after reopening a lazily-loaded store.
443    pub fn embed_existing(&self, path: &str) -> IntegrationResult<bool> {
444        // Lock-free fast path.
445        let sig = match self.path_map.get(path) {
446            Some(r) => r.value().clone(),
447            None => {
448                return Err(IntegrationError::NotFound(format!(
449                    "Node at path {} not found",
450                    path
451                )))
452            }
453        };
454        if !sig.is_stub() {
455            return Ok(false);
456        }
457
458        // Ancestors first. The recursive call acquires and releases its own
459        // parent stripe before this frame takes any lock — no nested guards,
460        // no ordering hazard.
461        let parent_path = self.path_ops.parent_path(path);
462        if let Some(p) = &parent_path {
463            if p.as_str() != path {
464                self.embed_existing(p)?;
465            }
466        }
467
468        let parent_signature = self.resolve_parent(path, &parent_path)?;
469        if let Some(ps) = &parent_signature {
470            if ps.is_stub() {
471                // A concurrent delete+reinsert put a fresh data-only parent
472                // back between our recursion and this lookup. Surface it
473                // rather than embedding against a positionless parent.
474                return Err(IntegrationError::OperationFailed(format!(
475                    "cannot embed '{}': parent lost its embedding concurrently",
476                    path
477                )));
478            }
479        }
480        let parent_uid = parent_signature.as_ref().map(|s| s.unique_id());
481        let _stripe_guard = parent_uid.as_deref().map(|uid| self.parent_locks.lock(uid));
482
483        // Re-check under the lock: a racing embed may have won, or a racing
484        // delete may have removed the node.
485        let old_sig = match self.path_map.get(path) {
486            Some(r) => r.value().clone(),
487            None => {
488                return Err(IntegrationError::NotFound(format!(
489                    "Node at path {} not found",
490                    path
491                )))
492            }
493        };
494        if !old_sig.is_stub() {
495            return Ok(false);
496        }
497        let old_uid = old_sig.unique_id();
498
499        // Preserve the node's identity.
500        let node = self
501            .tensor_network
502            .get_node_by_signature(&old_sig)
503            .ok_or_else(|| {
504                IntegrationError::OperationFailed(format!(
505                    "data-only node '{}' missing from the node map",
506                    path
507                ))
508            })?;
509        let metadata = node.metadata().clone();
510        let value = node.value().to_vec();
511        let coords = node.semantic_coords().to_vec();
512
513        let level = self.path_depth(path);
514        let signature = self
515            .tensor_network
516            .add_node(metadata, value, parent_signature.as_ref(), level)
517            .ok_or_else(|| {
518                IntegrationError::OperationFailed(format!(
519                    "failed to embed node '{}'",
520                    path
521                ))
522            })?;
523        let new_uid = signature.unique_id();
524
525        // Coordinates before the visibility swap: a concurrent semantic
526        // query never misses the node.
527        if !coords.is_empty() {
528            self.tensor_network.set_node_semantic(&new_uid, coords);
529        }
530
531        // Swap visibility to the embedded node, then retire the stub entry.
532        self.id_to_path.insert(new_uid, path.to_string());
533        self.path_map.insert(path.to_string(), signature);
534        self.id_to_path.remove(&old_uid);
535        self.tensor_network.remove_detached_node(&old_uid);
536
537        Ok(true)
538    }
539
540    /// Get a node by path (returns cloned value).
541    pub fn get(&self, path: &str) -> IntegrationResult<CompressedNode> {
542        let signature = self
543            .path_map
544            .get(path)
545            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
546
547        self.tensor_network
548            .get_node_by_signature(signature.value())
549            .ok_or_else(|| {
550                IntegrationError::NotFound(format!(
551                    "Node with signature {} not found",
552                    signature.value().hash()
553                ))
554            })
555    }
556
557    /// Update the value of a node at the specified path.
558    pub fn update_value(&self, path: &str, value: Vec<u8>) -> IntegrationResult<()> {
559        let uid = self
560            .path_map
561            .get(path)
562            .map(|r| r.value().unique_id())
563            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
564
565        if self.tensor_network.update_node_value(&uid, value) {
566            Ok(())
567        } else {
568            Err(IntegrationError::NotFound(format!("Node with uid {} not found in network", uid)))
569        }
570    }
571
572    /// Set a metadata key-value pair on a node.
573    pub fn set_node_metadata(&self, path: &str, key: &str, value: &str) -> IntegrationResult<()> {
574        let uid = self
575            .path_map
576            .get(path)
577            .map(|r| r.value().unique_id())
578            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
579
580        if self.tensor_network.set_node_metadata_entry(&uid, key, value) {
581            Ok(())
582        } else {
583            Err(IntegrationError::NotFound(format!("Node with uid {} not found in network", uid)))
584        }
585    }
586
587    /// Set semantic coordinates on a node (raw Q64.64 bytes, 16 bytes per dimension).
588    pub fn set_semantic(&self, path: &str, coords: Vec<u8>) -> IntegrationResult<()> {
589        let uid = self
590            .path_map
591            .get(path)
592            .map(|r| r.value().unique_id())
593            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
594
595        if self.tensor_network.set_node_semantic(&uid, coords) {
596            Ok(())
597        } else {
598            Err(IntegrationError::NotFound(format!("Node with uid {} not found in network", uid)))
599        }
600    }
601
602    /// Get semantic coordinates for a node (raw Q64.64 bytes).
603    pub fn get_semantic(&self, path: &str) -> IntegrationResult<Vec<u8>> {
604        let uid = self
605            .path_map
606            .get(path)
607            .map(|r| r.value().unique_id())
608            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
609
610        self.tensor_network
611            .get_node_semantic(&uid)
612            .ok_or_else(|| IntegrationError::NotFound(format!("Node with uid {} not found in network", uid)))
613    }
614
615    /// Delete a node at the specified path.
616    ///
617    /// A node with live children cannot be deleted (that would orphan them).
618    /// The check and the removal run under two stripe locks — the node's own
619    /// (which blocks a concurrent insert of a child *under* it, closing the
620    /// has-no-children TOCTOU) and its parent's (which serializes the parent's
621    /// child-list mutation against sibling inserts/deletes). The two stripes
622    /// are taken in canonical order, so this never deadlocks against a
623    /// concurrent delete.
624    pub fn delete(&self, path: &str) -> IntegrationResult<()> {
625        // Resolve the node and its parent before locking.
626        let node_uid = match self.path_map.get(path) {
627            Some(r) => r.value().unique_id(),
628            None => {
629                return Err(IntegrationError::NotFound(format!(
630                    "Node at path {} not found",
631                    path
632                )))
633            }
634        };
635        let parent_uid = self
636            .path_ops
637            .parent_path(path)
638            .and_then(|pp| self.path_map.get(&pp).map(|s| s.unique_id()));
639
640        // Hold the node's stripe (and the parent's, if any) for the whole
641        // check-then-remove. `_guards` keeps both alive to the end of scope.
642        let _guards = match &parent_uid {
643            Some(puid) => {
644                let (g1, g2) = self.parent_locks.lock_two(&node_uid, puid);
645                (Some(g1), g2)
646            }
647            None => (Some(self.parent_locks.lock(&node_uid)), None),
648        };
649
650        // Re-check under the lock: the node may have been removed, or gained a
651        // child, since the pre-lock read.
652        if !self.path_map.contains_key(path) {
653            return Err(IntegrationError::NotFound(format!(
654                "Node at path {} not found",
655                path
656            )));
657        }
658        let children = self.list_children(path)?;
659        if !children.is_empty() {
660            return Err(IntegrationError::ValidationFailed(format!(
661                "Cannot delete node at {} because it has {} children",
662                path,
663                children.len()
664            )));
665        }
666
667        // Remove from path map, reverse map, and spatial index. The parent's
668        // unique id is passed so the parent's child list drops this node even
669        // when the node has no power cell (data-only nodes).
670        if let Some((_, sig)) = self.path_map.remove(path) {
671            let unique_id = sig.unique_id();
672            self.id_to_path.remove(&unique_id);
673            self.tensor_network
674                .unregister_node_with_parent(&unique_id, parent_uid.as_deref());
675        }
676
677        Ok(())
678    }
679
680    /// List children of a node at the specified path (cloned).
681    pub fn list_children(&self, path: &str) -> IntegrationResult<Vec<CompressedNode>> {
682        let signature = self
683            .path_map
684            .get(path)
685            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
686
687        let children = self.tensor_network.children_of(signature.value());
688
689        // Filter out deleted nodes (removed from path_map but still in tensor network)
690        let result = children
691            .into_iter()
692            .filter(|child| self.path_map.contains_key(&child.metadata().key))
693            .collect();
694
695        Ok(result)
696    }
697
698    /// List all node paths under a specified path.
699    pub fn list_subtree(&self, path: &str) -> IntegrationResult<Vec<String>> {
700        let prefix = if path.ends_with('/') {
701            path.to_string()
702        } else {
703            format!("{}/", path)
704        };
705
706        let mut result = Vec::new();
707        for entry in self.path_map.iter() {
708            let node_path = entry.key();
709            if node_path.as_str() != path && (path == "/" || node_path.starts_with(&prefix)) {
710                result.push(node_path.clone());
711            }
712        }
713
714        Ok(result)
715    }
716
717    /// Get the path depth (number of components).
718    fn path_depth(&self, path: &str) -> u32 {
719        if path == "/" {
720            return 0;
721        }
722        self.path_ops.split_path(path).len() as u32
723    }
724
725    /// Check if a path exists.
726    pub fn exists(&self, path: &str) -> bool {
727        self.path_map.contains_key(path)
728    }
729
730    /// Get the number of nodes in the tree.
731    pub fn node_count(&self) -> usize {
732        self.path_map.len()
733    }
734
735    /// Get tree statistics.
736    pub fn stats(&self) -> HashMap<String, String> {
737        let mut stats = HashMap::new();
738        stats.insert("node_count".to_string(), self.node_count().to_string());
739        stats.insert(
740            "dimension".to_string(),
741            self.config.dimension().to_string(),
742        );
743        stats
744    }
745
746    /// Validate the tree structure.
747    ///
748    /// Checks all structural invariants: parent-child consistency,
749    /// path_map ↔ id_to_path bidirectionality, and tensor network integrity.
750    pub fn validate(&self) -> bool {
751        // Empty tree is valid
752        if self.path_map.is_empty() {
753            return true;
754        }
755
756        // Check that tensor network is valid
757        if !self.tensor_network.validate_network() {
758            return false;
759        }
760
761        // All non-root paths have valid parents
762        for entry in self.path_map.iter() {
763            let path = entry.key();
764            if path == "/" {
765                continue;
766            }
767            if let Some(parent_path) = self.path_ops.parent_path(path) {
768                if !self.path_map.contains_key(&parent_path) {
769                    return false;
770                }
771            }
772        }
773
774        // path_map ↔ id_to_path bidirectional consistency
775        for entry in self.path_map.iter() {
776            let path = entry.key();
777            let sig = entry.value();
778            let uid = sig.unique_id();
779            match self.id_to_path.get(&uid) {
780                Some(reverse_path) if reverse_path.value() == path => {},
781                _ => return false,
782            }
783        }
784        for entry in self.id_to_path.iter() {
785            let uid = entry.key();
786            let path = entry.value();
787            match self.path_map.get(path.as_str()) {
788                Some(sig) if sig.value().unique_id() == *uid => {},
789                _ => return false,
790            }
791        }
792
793        // path_map and id_to_path must have the same size
794        if self.path_map.len() != self.id_to_path.len() {
795            return false;
796        }
797
798        true
799    }
800
801    /// Get the underlying tensor network (for spatial queries).
802    pub fn tensor_network(&self) -> &HyperbolicTensorNetwork {
803        &self.tensor_network
804    }
805
806    /// Resolve a unique_id to a path.
807    pub fn path_for_id(&self, unique_id: &str) -> Option<String> {
808        self.id_to_path.get(unique_id).map(|r| r.value().clone())
809    }
810
811    /// The hyperbolic (Poincaré) position of a stored node.
812    ///
813    /// Errors with `NotFound` for unknown paths and `OperationFailed` for
814    /// data-only nodes, which have no geometric embedding.
815    pub fn position(&self, path: &str) -> IntegrationResult<super::hyperbolic_geometry::HyperbolicPoint> {
816        let sig = self
817            .path_map
818            .get(path)
819            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
820        let unique_id = sig.value().unique_id();
821        drop(sig);
822        self.tensor_network.get_point(&unique_id).ok_or_else(|| {
823            IntegrationError::OperationFailed(format!(
824                "node {} has no geometric embedding (data-only)",
825                path
826            ))
827        })
828    }
829
830    /// Get the path operations.
831    pub fn path_ops(&self) -> &dyn PathOperations {
832        &*self.path_ops
833    }
834
835    /// Find the nearest stored node to an arbitrary Poincaré disk point.
836    ///
837    /// Uses the Nielsen power diagram grid for O(1) lookup.
838    /// Returns (path, hyperbolic_distance).
839    pub fn nearest_neighbor_point(&self, query: &super::hyperbolic_geometry::HyperbolicPoint) -> IntegrationResult<(String, g_math::fixed_point::FixedPoint)> {
840        let (uid, dist) = self.tensor_network
841            .nearest_neighbor_point(query)
842            .ok_or_else(|| IntegrationError::OperationFailed(
843                "No nodes in tree for nearest neighbor query".to_string()
844            ))?;
845
846        let path = self.id_to_path.get(&uid)
847            .ok_or_else(|| IntegrationError::NotFound(
848                format!("No path for unique_id {}", uid)
849            ))?;
850
851        Ok((path.value().clone(), dist))
852    }
853
854    /// Find the k nearest stored nodes to an arbitrary Poincaré disk point.
855    ///
856    /// Returns `(path, hyperbolic_distance)` sorted by ascending distance.
857    pub fn nearest_neighbor_point_k(&self, query: &super::hyperbolic_geometry::HyperbolicPoint, k: usize) -> IntegrationResult<Vec<(String, g_math::fixed_point::FixedPoint)>> {
858        let results = self.tensor_network.nearest_neighbor_point_k(query, k);
859        if results.is_empty() {
860            return Err(IntegrationError::OperationFailed(
861                "No nodes in tree for nearest neighbor query".to_string()
862            ));
863        }
864
865        let mut paths = Vec::with_capacity(results.len());
866        for (uid, dist) in results {
867            if let Some(p) = self.id_to_path.get(&uid) {
868                paths.push((p.value().clone(), dist));
869            }
870        }
871        Ok(paths)
872    }
873
874    /// Find the k nearest stored nodes to the given path's position in hyperbolic space.
875    /// Returns paths sorted by ascending hyperbolic distance.
876    pub fn find_nearest(&self, path: &str, k: usize) -> IntegrationResult<Vec<(String, g_math::fixed_point::FixedPoint)>> {
877        let sig = self
878            .path_map
879            .get(path)
880            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
881
882        let unique_id = sig.value().unique_id();
883        // Drop the DashMap guard before calling tensor_network
884        drop(sig);
885
886        let point = self
887            .tensor_network
888            .get_point(&unique_id)
889            .ok_or_else(|| IntegrationError::OperationFailed("Point not found in spatial index".to_string()))?;
890
891        let results = self
892            .tensor_network
893            .hash_table()
894            .find_nearest_nodes(&point, k + 1); // +1 to exclude self
895
896        let mut paths = Vec::new();
897        for (uid, dist) in results {
898            if uid == unique_id {
899                continue; // Skip self
900            }
901            if let Some(p) = self.id_to_path.get(&uid) {
902                paths.push((p.value().clone(), dist));
903            }
904        }
905        paths.truncate(k);
906        Ok(paths)
907    }
908
909    // -----------------------------------------------------------------------
910    // Semantic dimensional distance queries
911    // -----------------------------------------------------------------------
912
913    /// Find the k nearest nodes by Euclidean distance across a dimensional slice
914    /// of the semantic coordinate space.
915    ///
916    /// `query_coords`: raw Q64.64 bytes representing the query point.
917    /// `k`: number of results.
918    /// `dim_range`: which dimensions to compare (e.g., `16..33` for category axes).
919    ///
920    /// Returns `(path, distance)` sorted by distance ascending.
921    pub fn nearest_semantic(
922        &self,
923        query_coords: &[u8],
924        k: usize,
925        dim_range: &Range<usize>,
926    ) -> IntegrationResult<Vec<(String, g_math::fixed_point::FixedPoint)>> {
927        // The network returns node keys (== the paths registered at insert),
928        // already sorted ascending by (distance, key).
929        Ok(self.tensor_network.nearest_semantic(query_coords, k, dim_range))
930    }
931
932    /// Find the k nearest nodes to an existing node by semantic dimensional distance.
933    ///
934    /// Convenience wrapper: reads the node's semantic coordinates, then calls
935    /// `nearest_semantic`. The queried node is excluded from results.
936    pub fn neighbors_semantic(
937        &self,
938        path: &str,
939        k: usize,
940        dim_range: &Range<usize>,
941    ) -> IntegrationResult<Vec<(String, g_math::fixed_point::FixedPoint)>> {
942        let coords = self.get_semantic(path)?;
943
944        // Request k+1 to account for self, then filter.
945        // The network returns node keys directly; resolve the queried
946        // node's canonical key through path_map so a non-normalized input
947        // still matches its own entry.
948        let results = self.tensor_network.nearest_semantic(&coords, k + 1, dim_range);
949
950        let self_key = self
951            .path_map
952            .get(path)
953            .and_then(|r| self.id_to_path.get(&r.value().unique_id()).map(|p| p.value().clone()));
954
955        let mut paths = Vec::with_capacity(k);
956        for (key, dist) in results {
957            if Some(&key) == self_key.as_ref() {
958                continue; // Skip self
959            }
960            paths.push((key, dist));
961            if paths.len() >= k {
962                break;
963            }
964        }
965        Ok(paths)
966    }
967
968    /// Find all stored nodes within hyperbolic radius of the given path.
969    /// Returns paths and their distances.
970    pub fn find_in_radius(&self, path: &str, radius: g_math::fixed_point::FixedPoint) -> IntegrationResult<Vec<(String, g_math::fixed_point::FixedPoint)>> {
971        let sig = self
972            .path_map
973            .get(path)
974            .ok_or_else(|| IntegrationError::NotFound(format!("Node at path {} not found", path)))?;
975
976        let unique_id = sig.value().unique_id();
977        drop(sig);
978
979        let point = self
980            .tensor_network
981            .get_point(&unique_id)
982            .ok_or_else(|| IntegrationError::OperationFailed("Point not found in spatial index".to_string()))?;
983
984        let results = self
985            .tensor_network
986            .hash_table()
987            .find_nodes_in_radius(&point, radius);
988
989        let mut paths = Vec::new();
990        for (uid, dist) in results {
991            if uid == unique_id {
992                continue; // Skip self
993            }
994            if let Some(p) = self.id_to_path.get(&uid) {
995                paths.push((p.value().clone(), dist));
996            }
997        }
998        Ok(paths)
999    }
1000}
1001
1002impl Debug for HyperbolicTreeTensor {
1003    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1004        write!(f, "HyperbolicTreeTensor(nodes={})", self.node_count())
1005    }
1006}
1007
1008/// Thread-safe wrapper for the HyperbolicTreeTensor.
1009///
1010/// No outer RwLock needed: all interior state uses DashMap, Mutex, or RwLock
1011/// for fine-grained concurrency. Methods take `&self`.
1012pub type SharedHTT = Arc<HyperbolicTreeTensor>;
1013
1014#[cfg(test)]
1015mod tests {
1016    use super::*;
1017
1018    #[test]
1019    fn test_path_operations() {
1020        let path_ops = DefaultPathOps;
1021
1022        let components = path_ops.split_path("/a/b/c");
1023        assert_eq!(
1024            components,
1025            vec!["a".to_string(), "b".to_string(), "c".to_string()]
1026        );
1027
1028        let path = path_ops.join_path(&["a".to_string(), "b".to_string(), "c".to_string()]);
1029        assert_eq!(path, "/a/b/c");
1030
1031        assert_eq!(path_ops.parent_path("/a/b/c"), Some("/a/b".to_string()));
1032        assert_eq!(path_ops.parent_path("/a"), Some("/".to_string()));
1033        assert_eq!(path_ops.parent_path("/"), None);
1034
1035        assert_eq!(path_ops.last_component("/a/b/c"), Some("c".to_string()));
1036        assert_eq!(path_ops.last_component("/a"), Some("a".to_string()));
1037        assert_eq!(path_ops.last_component("/"), None);
1038    }
1039
1040    #[test]
1041    fn test_tree_tensor_creation() {
1042        let config = HTTConfig::default();
1043        let tree = HyperbolicTreeTensor::new(config);
1044        assert_eq!(tree.node_count(), 0);
1045    }
1046
1047    #[test]
1048    fn test_tree_tensor_operations() {
1049        let config = HTTConfig::default();
1050        let tree = HyperbolicTreeTensor::new(config);
1051
1052        // Insert root node
1053        tree.insert("/", vec![], None).unwrap();
1054        assert_eq!(tree.node_count(), 1);
1055        assert!(tree.exists("/"));
1056
1057        // Get root node
1058        let root = tree.get("/").unwrap();
1059        assert_eq!(root.metadata().key, "/");
1060
1061        // Insert child nodes
1062        tree.insert("/child1", b"child1 data".to_vec(), None).unwrap();
1063        tree.insert("/child2", b"child2 data".to_vec(), None).unwrap();
1064        assert_eq!(tree.node_count(), 3);
1065
1066        // Insert grandchild
1067        tree.insert("/child1/grandchild", b"grandchild data".to_vec(), None).unwrap();
1068        assert_eq!(tree.node_count(), 4);
1069
1070        // List children
1071        let children = tree.list_children("/").unwrap();
1072        assert_eq!(children.len(), 2);
1073
1074        let child_keys: Vec<&str> = children.iter().map(|c| c.metadata().key.as_str()).collect();
1075        assert!(child_keys.contains(&"/child1"));
1076        assert!(child_keys.contains(&"/child2"));
1077
1078        // Update a node
1079        tree.update_value("/child1", b"updated data".to_vec()).unwrap();
1080        let updated = tree.get("/child1").unwrap();
1081        assert_eq!(updated.value(), b"updated data");
1082
1083        // List subtree
1084        let subtree = tree.list_subtree("/").unwrap();
1085        assert_eq!(subtree.len(), 3); // child1, child2, child1/grandchild
1086
1087        // Try to delete node with children (should fail)
1088        let result = tree.delete("/child1");
1089        assert!(result.is_err());
1090
1091        // Delete leaf node
1092        tree.delete("/child1/grandchild").unwrap();
1093        assert_eq!(tree.node_count(), 3);
1094
1095        // Now delete former parent
1096        tree.delete("/child1").unwrap();
1097        assert_eq!(tree.node_count(), 2);
1098    }
1099
1100    #[test]
1101    fn test_tree_validation() {
1102        let config = HTTConfig::default();
1103        let tree = HyperbolicTreeTensor::new(config);
1104
1105        // Empty tree should be valid
1106        assert!(tree.validate());
1107
1108        // Add some nodes
1109        tree.insert("/", vec![], None).unwrap();
1110        tree.insert("/child", b"child data".to_vec(), None).unwrap();
1111
1112        // Tree with nodes should still be valid
1113        assert!(tree.validate());
1114    }
1115
1116    #[test]
1117    fn test_id_to_path_mapping() {
1118        let config = HTTConfig::default();
1119        let tree = HyperbolicTreeTensor::new(config);
1120
1121        tree.insert("/", vec![], None).unwrap();
1122        tree.insert("/test", b"test".to_vec(), None).unwrap();
1123
1124        // Verify id_to_path works
1125        let uid = tree.path_map.get("/test").unwrap().value().unique_id();
1126        let resolved_path = tree.path_for_id(&uid);
1127        assert_eq!(resolved_path, Some("/test".to_string()));
1128    }
1129
1130    #[test]
1131    fn test_find_nearest() {
1132        let config = HTTConfig::default();
1133        let tree = HyperbolicTreeTensor::new(config);
1134
1135        tree.insert("/", vec![], None).unwrap();
1136        tree.insert("/a", b"a".to_vec(), None).unwrap();
1137        tree.insert("/b", b"b".to_vec(), None).unwrap();
1138        tree.insert("/c", b"c".to_vec(), None).unwrap();
1139        tree.insert("/a/child", b"ac".to_vec(), None).unwrap();
1140
1141        // Find nearest to /a — should return other nodes sorted by distance
1142        let nearest = tree.find_nearest("/a", 3).unwrap();
1143        assert!(!nearest.is_empty());
1144        assert!(nearest.len() <= 3);
1145
1146        // Results should not include /a itself
1147        let paths: Vec<&str> = nearest.iter().map(|(p, _)| p.as_str()).collect();
1148        assert!(!paths.contains(&"/a"));
1149
1150        // Distances should be in ascending order
1151        for i in 1..nearest.len() {
1152            assert!(nearest[i].1 >= nearest[i - 1].1);
1153        }
1154    }
1155
1156    #[test]
1157    fn test_find_in_radius() {
1158        use g_math::fixed_point::FixedPoint;
1159
1160        let config = HTTConfig::default();
1161        let tree = HyperbolicTreeTensor::new(config);
1162
1163        tree.insert("/", vec![], None).unwrap();
1164        tree.insert("/a", b"a".to_vec(), None).unwrap();
1165        tree.insert("/b", b"b".to_vec(), None).unwrap();
1166
1167        // With a very large radius, should find other nodes
1168        let large_radius = FixedPoint::from_int(10);
1169        let results = tree.find_in_radius("/", large_radius).unwrap();
1170        assert!(results.len() >= 2, "Expected at least 2 nodes within large radius, got {}", results.len());
1171
1172        // With a tiny radius, should find few or no nodes
1173        let tiny_radius = FixedPoint::from_int(1) / FixedPoint::from_int(10000);
1174        let results = tree.find_in_radius("/", tiny_radius).unwrap();
1175        // The root is at origin, children are close but not at zero distance
1176        // So with a very tiny radius, we might find 0 or a few
1177        assert!(results.len() <= 2);
1178    }
1179
1180    #[test]
1181    fn test_delete_unregisters_spatial() {
1182        let config = HTTConfig::default();
1183        let tree = HyperbolicTreeTensor::new(config);
1184
1185        tree.insert("/", vec![], None).unwrap();
1186        tree.insert("/leaf", b"leaf".to_vec(), None).unwrap();
1187
1188        // Get the unique_id before deletion
1189        let unique_id = tree.path_map.get("/leaf").unwrap().value().unique_id();
1190
1191        // Verify point exists in tensor network
1192        assert!(tree.tensor_network().get_point(&unique_id).is_some());
1193
1194        // Delete the leaf
1195        tree.delete("/leaf").unwrap();
1196
1197        // Point should be removed from spatial index
1198        assert!(tree.tensor_network().get_point(&unique_id).is_none());
1199        assert!(tree.path_for_id(&unique_id).is_none());
1200    }
1201}