1use std::collections::HashMap;
18use std::fmt;
19use std::ops::Range;
20
21use g_math::fixed_point::FixedPoint;
22
23use super::config::HTTStorageConfig;
24use super::constants::{OUTLIER_KNN, OUTLIER_MIN_POPULATION};
25use super::metric_tree::{EuclideanMetric, MetricVpTree};
26use super::storage::HTTStorage;
27use super::tensor_network::HyperbolicTensorNetwork;
28use super::tree_tensor::IntegrationError;
29
30#[derive(Debug)]
36pub enum StoreError {
37 NotFound(String),
39 AlreadyExists(String),
41 InvalidOperation(String),
43 Internal(String),
45}
46
47impl fmt::Display for StoreError {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 StoreError::NotFound(msg) => write!(f, "not found: {}", msg),
51 StoreError::AlreadyExists(msg) => write!(f, "already exists: {}", msg),
52 StoreError::InvalidOperation(msg) => write!(f, "invalid operation: {}", msg),
53 StoreError::Internal(msg) => write!(f, "internal error: {}", msg),
54 }
55 }
56}
57
58impl std::error::Error for StoreError {}
59
60#[derive(Debug, Clone, PartialEq)]
64pub struct SemanticOutlier {
65 pub key: String,
67 pub avg_knn_distance: FixedPoint,
69 pub z_score: FixedPoint,
72 pub nearest_peer: String,
74 pub nearest_distance: FixedPoint,
76}
77
78impl From<IntegrationError> for StoreError {
79 fn from(e: IntegrationError) -> Self {
80 match e {
81 IntegrationError::NotFound(msg) => StoreError::NotFound(msg),
82 IntegrationError::AlreadyExists(msg) => StoreError::AlreadyExists(msg),
83 IntegrationError::ValidationFailed(msg) | IntegrationError::ConfigurationError(msg) => {
84 StoreError::InvalidOperation(msg)
85 }
86 IntegrationError::OperationFailed(msg)
87 | IntegrationError::DeserializationError(msg)
88 | IntegrationError::LockError(msg) => StoreError::Internal(msg),
89 }
90 }
91}
92
93pub struct StoreConfig {
102 capacity: usize,
103 tau: FixedPoint,
104}
105
106impl StoreConfig {
107 pub fn new() -> Self {
109 Self { capacity: 10_000, tau: FixedPoint::from_int(0) }
110 }
111
112 pub fn capacity(mut self, n: usize) -> Self {
117 self.capacity = n;
118 self
119 }
120
121 pub fn tau(mut self, t: FixedPoint) -> Self {
128 self.tau = t;
129 self
130 }
131
132 fn to_htt_config(&self) -> HTTStorageConfig {
133 HTTStorageConfig {
134 dimension: 4,
135 max_memory_nodes: self.capacity,
136 cache_size: std::cmp::max(self.capacity / 10, 10),
137 storage_path: None,
138 flush_interval: 60,
139 optimize_on_shutdown: true,
140 grid_resolution: 0,
141 tau: self.tau,
142 }
143 }
144}
145
146impl Default for StoreConfig {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152#[derive(Debug, Clone)]
158pub enum QueryResult {
159 Entry {
161 key: String,
163 data: Vec<u8>,
165 meta: HashMap<String, String>,
167 },
168 Count(usize),
170 Keys(Vec<String>),
172}
173
174pub trait QueryAdapter: Send + Sync {
180 fn execute(&self, store: &Store, query: &str) -> Result<Vec<QueryResult>, StoreError>;
182}
183
184pub struct Store {
213 inner: HTTStorage,
214}
215
216impl Store {
217 pub fn new() -> Self {
219 Self::with_config(StoreConfig::new())
220 }
221
222 pub fn with_config(config: StoreConfig) -> Self {
224 Self {
225 inner: HTTStorage::new(config.to_htt_config()),
226 }
227 }
228
229 pub fn put(&self, key: &str, data: &[u8]) -> Result<(), StoreError> {
233 self.inner.store(key, data, None)?;
234 Ok(())
235 }
236
237 pub fn put_data_only(&self, key: &str, data: &[u8]) -> Result<(), StoreError> {
245 self.inner.store_data_only(key, data, None)?;
246 Ok(())
247 }
248
249 pub fn put_positioned(&self, key: &str, data: &[u8], child_index: u32) -> Result<(), StoreError> {
254 self.inner.store_positioned(key, data, None, child_index)?;
255 Ok(())
256 }
257
258 pub fn get(&self, key: &str) -> Result<Vec<u8>, StoreError> {
260 Ok(self.inner.retrieve(key)?)
261 }
262
263 pub fn remove(&self, key: &str) -> Result<(), StoreError> {
265 self.inner.delete(key)?;
266 Ok(())
267 }
268
269 pub fn exists(&self, key: &str) -> bool {
271 self.inner.exists(key)
272 }
273
274 pub fn children(&self, path: &str) -> Result<Vec<String>, StoreError> {
278 let htt = self.inner.shared_htt();
279 let nodes = htt.list_children(path)?;
280 Ok(nodes.into_iter().map(|n| n.metadata().key.clone()).collect())
281 }
282
283 pub fn list(&self, prefix: &str) -> Result<Vec<String>, StoreError> {
285 Ok(self.inner.list(prefix)?)
286 }
287
288 pub fn set_meta(&self, key: &str, name: &str, value: &str) -> Result<(), StoreError> {
290 self.inner.set_metadata(key, name, value)?;
291 Ok(())
292 }
293
294 pub fn get_meta(&self, key: &str) -> Result<HashMap<String, String>, StoreError> {
296 Ok(self.inner.get_metadata(key)?)
297 }
298
299 pub fn set_semantic(&self, key: &str, coords: Vec<u8>) -> Result<(), StoreError> {
309 if coords.len() % 16 != 0 {
310 return Err(StoreError::InvalidOperation(format!(
311 "semantic coordinates must be a multiple of 16 bytes (one Q64.64 value per dimension); got {} bytes",
312 coords.len()
313 )));
314 }
315 self.inner.set_semantic(key, coords)?;
316 Ok(())
317 }
318
319 pub fn get_semantic(&self, key: &str) -> Result<Vec<u8>, StoreError> {
323 Ok(self.inner.get_semantic(key)?)
324 }
325
326 pub fn nearest(&self, coords: &[FixedPoint]) -> Result<(String, FixedPoint), StoreError> {
333 Ok(self.inner.nearest_neighbor_point(coords)?)
334 }
335
336 pub fn nearest_k(&self, coords: &[FixedPoint], k: usize) -> Result<Vec<(String, FixedPoint)>, StoreError> {
347 Ok(self.inner.nearest_neighbor_point_k(coords, k)?)
348 }
349
350 pub fn neighbors(&self, path: &str, k: usize) -> Result<Vec<String>, StoreError> {
358 Ok(self.inner.find_nearest(path, k)?)
359 }
360
361 pub fn nearest_semantic(
387 &self,
388 query_coords: &[u8],
389 k: usize,
390 dim_range: Range<usize>,
391 ) -> Result<Vec<(String, FixedPoint)>, StoreError> {
392 if query_coords.len() % 16 != 0 {
393 return Err(StoreError::InvalidOperation(format!(
394 "semantic query coordinates must be a multiple of 16 bytes; got {} bytes",
395 query_coords.len()
396 )));
397 }
398 let results = self.inner.nearest_semantic(query_coords, k, &dim_range)?;
399 Ok(results)
400 }
401
402 pub fn neighbors_semantic(
412 &self,
413 path: &str,
414 k: usize,
415 dim_range: Range<usize>,
416 ) -> Result<Vec<(String, FixedPoint)>, StoreError> {
417 let results = self.inner.neighbors_semantic(path, k, &dim_range)?;
418 Ok(results)
419 }
420
421 pub fn find_similar(
429 &self,
430 key: &str,
431 k: usize,
432 dim_range: Range<usize>,
433 ) -> Result<Vec<(String, FixedPoint)>, StoreError> {
434 self.neighbors_semantic(key, k, dim_range)
435 }
436
437 pub fn find_outliers(
463 &self,
464 prefix: &str,
465 z_threshold: FixedPoint,
466 dim_range: Range<usize>,
467 ) -> Result<Vec<SemanticOutlier>, StoreError> {
468 if z_threshold <= FixedPoint::from_int(0) {
469 return Err(StoreError::InvalidOperation(format!(
470 "z_threshold must be a positive number; got {}",
471 z_threshold.to_f64()
472 )));
473 }
474
475 let mut keys = self.list(prefix)?;
478 keys.sort();
479 let entries: Vec<(String, Vec<FixedPoint>)> = keys
480 .into_iter()
481 .filter_map(|key| {
482 let coords = self.inner.get_semantic(&key).ok()?;
483 if coords.is_empty() {
484 return None;
485 }
486 Some((
487 key,
488 HyperbolicTensorNetwork::decode_semantic_slice(&coords, &dim_range),
489 ))
490 })
491 .collect();
492
493 if entries.len() < OUTLIER_MIN_POPULATION {
494 return Ok(Vec::new());
495 }
496
497 let tree = MetricVpTree::build(entries.clone(), &EuclideanMetric);
501 let k = OUTLIER_KNN.min(entries.len() - 1);
502
503 let zero = FixedPoint::from_int(0);
505 let mut scored: Vec<(String, FixedPoint, String, FixedPoint)> = entries
506 .iter()
507 .map(|(key, point)| {
508 let peers: Vec<(String, FixedPoint)> = tree
509 .knn(point, k + 1, &EuclideanMetric)
510 .into_iter()
511 .filter(|(id, _)| id != key)
512 .take(k)
513 .collect();
514 let sum = peers.iter().fold(zero, |acc, (_, d)| acc + *d);
515 let avg = sum / FixedPoint::from_int(k as i32);
516 let (nearest_peer, nearest_distance) = peers[0].clone();
517 (key.clone(), avg, nearest_peer, nearest_distance)
518 })
519 .collect();
520
521 let n = FixedPoint::from_int(scored.len() as i32);
525 let mean = scored.iter().fold(zero, |acc, (_, avg, _, _)| acc + *avg) / n;
526 let variance = scored
527 .iter()
528 .fold(zero, |acc, (_, avg, _, _)| {
529 let d = *avg - mean;
530 acc + d * d
531 })
532 / n;
533 let stdev = variance.sqrt();
534 if stdev <= FixedPoint::from_raw(1) {
535 return Ok(Vec::new()); }
537
538 scored.sort_by(|a, b| {
539 b.1.partial_cmp(&a.1)
540 .unwrap_or(std::cmp::Ordering::Equal)
541 .then_with(|| a.0.cmp(&b.0))
542 });
543
544 Ok(scored
545 .into_iter()
546 .filter_map(|(key, avg, nearest_peer, nearest_distance)| {
547 let z_score = (avg - mean) / stdev;
548 (z_score > z_threshold).then_some(SemanticOutlier {
549 key,
550 avg_knn_distance: avg,
551 z_score,
552 nearest_peer,
553 nearest_distance,
554 })
555 })
556 .collect())
557 }
558
559 pub fn embed_existing(&self, key: &str) -> Result<bool, StoreError> {
572 Ok(self.inner.embed_existing(key)?)
573 }
574
575 pub fn embed_all(&self, prefix: &str) -> Result<usize, StoreError> {
579 let mut upgraded = 0;
580 if self.exists(prefix) && self.embed_existing(prefix)? {
581 upgraded += 1;
582 }
583 let mut keys = self.list(prefix)?;
584 keys.sort();
585 for key in keys {
586 if self.embed_existing(&key)? {
587 upgraded += 1;
588 }
589 }
590 Ok(upgraded)
591 }
592
593 pub fn position(&self, key: &str) -> Result<Vec<FixedPoint>, StoreError> {
597 let point = self.inner.position(key)?;
598 Ok(point.coords().iter().copied().collect())
599 }
600
601 pub(crate) fn position_fixed(
604 &self,
605 key: &str,
606 ) -> Result<crate::hyperbolic_geometry::HyperbolicPoint, StoreError> {
607 Ok(self.inner.position(key)?)
608 }
609
610 pub fn semantic_epoch(&self) -> u64 {
615 self.inner.semantic_epoch()
616 }
617
618 pub fn semantic_distance(
623 coords_a: &[u8],
624 coords_b: &[u8],
625 dim_range: Range<usize>,
626 ) -> FixedPoint {
627 HyperbolicTensorNetwork::semantic_distance(coords_a, coords_b, &dim_range)
628 }
629
630 pub fn find_within(&self, path: &str, radius: FixedPoint) -> Result<Vec<String>, StoreError> {
635 Ok(self.inner.find_in_radius(path, radius)?)
636 }
637
638 pub fn query(&self, adapter: &dyn QueryAdapter, query: &str) -> Result<Vec<QueryResult>, StoreError> {
643 adapter.execute(self, query)
644 }
645
646 pub fn len(&self) -> usize {
648 self.inner.node_count().saturating_sub(1) }
650
651 pub fn is_empty(&self) -> bool {
653 self.len() == 0
654 }
655
656 pub fn inner(&self) -> &HTTStorage {
658 &self.inner
659 }
660
661 #[deprecated(note = "All HTTStorage methods now take &self; use inner() instead")]
663 pub fn inner_mut(&mut self) -> &mut HTTStorage {
664 &mut self.inner
665 }
666}
667
668impl Default for Store {
669 fn default() -> Self {
670 Self::new()
671 }
672}
673
674#[cfg(test)]
679mod tests {
680
681fn fp(vals: &[f64]) -> Vec<g_math::fixed_point::FixedPoint> {
683 vals.iter().map(|&v| g_math::fixed_point::FixedPoint::from_f64(v)).collect()
684}
685
686 use super::*;
687
688 #[test]
689 fn test_new_store_is_empty() {
690 let store = Store::new();
691 assert!(store.is_empty());
692 assert_eq!(store.len(), 0);
693 }
694
695 #[test]
696 fn test_put_get_roundtrip() {
697 let store = Store::new();
698 store.put("/hello", b"world").unwrap();
699 assert_eq!(store.get("/hello").unwrap(), b"world");
700 }
701
702 #[test]
703 fn test_upsert() {
704 let store = Store::new();
705 store.put("/key", b"v1").unwrap();
706 store.put("/key", b"v2").unwrap();
707 assert_eq!(store.get("/key").unwrap(), b"v2");
708 }
709
710 #[test]
711 fn test_remove() {
712 let store = Store::new();
713 store.put("/tmp", b"data").unwrap();
714 assert!(store.exists("/tmp"));
715 store.remove("/tmp").unwrap();
716 assert!(!store.exists("/tmp"));
717 }
718
719 #[test]
720 fn test_exists() {
721 let store = Store::new();
722 assert!(!store.exists("/nope"));
723 store.put("/yes", b"").unwrap();
724 assert!(store.exists("/yes"));
725 }
726
727 #[test]
728 fn test_children() {
729 let store = Store::new();
730 store.put("/a/b", b"1").unwrap();
731 store.put("/a/c", b"2").unwrap();
732 store.put("/a/c/d", b"3").unwrap();
733
734 let kids = store.children("/a").unwrap();
735 assert!(kids.contains(&"/a/b".to_string()));
736 assert!(kids.contains(&"/a/c".to_string()));
737 assert!(!kids.contains(&"/a/c/d".to_string()));
739 }
740
741 #[test]
742 fn test_list() {
743 let store = Store::new();
744 store.put("/x/y", b"1").unwrap();
745 store.put("/x/z", b"2").unwrap();
746
747 let all = store.list("/x").unwrap();
748 assert!(all.contains(&"/x/y".to_string()));
749 assert!(all.contains(&"/x/z".to_string()));
750 }
751
752 #[test]
753 fn test_metadata() {
754 let store = Store::new();
755 store.put("/doc", b"content").unwrap();
756 store.set_meta("/doc", "author", "alice").unwrap();
757
758 let meta = store.get_meta("/doc").unwrap();
759 assert_eq!(meta.get("author"), Some(&"alice".to_string()));
760 }
761
762 #[test]
763 fn test_nearest() {
764 let store = Store::new();
765 store.put("/a", b"a").unwrap();
766 store.put("/b", b"b").unwrap();
767
768 let (path, dist) = store.nearest(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
769 assert_eq!(path, "/");
771 assert!(dist.to_f64() < 0.1);
772 }
773
774 #[test]
775 fn test_neighbors() {
776 let store = Store::new();
777 store.put("/a", b"a").unwrap();
778 store.put("/b", b"b").unwrap();
779 store.put("/c", b"c").unwrap();
780
781 let nbrs = store.neighbors("/a", 2).unwrap();
782 assert!(!nbrs.is_empty());
783 assert!(nbrs.len() <= 2);
784 assert!(!nbrs.contains(&"/a".to_string()));
785 }
786
787 #[test]
788 fn test_find_within() {
789 let store = Store::new();
790 store.put("/x", b"x").unwrap();
791 store.put("/y", b"y").unwrap();
792
793 let results = store.find_within("/x", g_math::fixed_point::FixedPoint::from_f64(10.0)).unwrap();
794 assert!(!results.is_empty());
795 }
796
797 #[test]
798 fn test_error_not_found() {
799 let store = Store::new();
800 let err = store.get("/missing").unwrap_err();
801 assert!(matches!(err, StoreError::NotFound(_)));
802 }
803
804 #[test]
805 fn test_len_tracking() {
806 let store = Store::new();
807 assert_eq!(store.len(), 0);
808
809 store.put("/one", b"1").unwrap();
810 assert_eq!(store.len(), 1);
811
812 store.put("/two", b"2").unwrap();
813 assert_eq!(store.len(), 2);
814
815 store.remove("/one").unwrap();
816 assert_eq!(store.len(), 1);
817 }
818
819 #[test]
820 fn test_inner_escape_hatch() {
821 let store = Store::new();
822 store.put("/test", b"data").unwrap();
823
824 assert!(store.inner().exists("/test"));
826
827 store.inner().store("/via_inner", b"inner", None).unwrap();
829 assert!(store.exists("/via_inner"));
830 }
831
832 #[test]
833 fn test_with_config() {
834 let config = StoreConfig::new().capacity(500);
835 let store = Store::with_config(config);
836 assert!(store.is_empty());
837 }
838
839 #[test]
840 fn test_tau_config() {
841 let store = Store::with_config(StoreConfig::new().capacity(1000).tau(FixedPoint::from_f64(0.8)));
842 store.put("/a", b"a").unwrap();
843 store.put("/b", b"b").unwrap();
844 store.put("/a/child", b"c").unwrap();
845 assert_eq!(store.len(), 3);
846
847 let (path, dist) = store.nearest(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
849 assert_eq!(path, "/");
850 assert!(dist.to_f64() < 0.1);
851 }
852
853 #[test]
854 fn test_tau_deep_tree() {
855 let store = Store::with_config(StoreConfig::new().tau(FixedPoint::from_f64(0.8)));
857 let mut path = String::new();
858 for i in 0..40 {
859 path = format!("{}/n{}", path, i);
860 store.put(&path, b"x").unwrap();
861 }
862 assert!(store.exists(&path));
863
864 let (nn, _) = store.nearest(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
865 assert!(store.exists(&nn));
866 }
867
868 #[test]
869 fn test_query_adapter() {
870 struct ChildrenAdapter;
872 impl QueryAdapter for ChildrenAdapter {
873 fn execute(&self, store: &Store, query: &str) -> Result<Vec<QueryResult>, StoreError> {
874 let children = store.children(query)?;
875 Ok(vec![QueryResult::Keys(children)])
876 }
877 }
878
879 let store = Store::new();
880 store.put("/a/b", b"1").unwrap();
881 store.put("/a/c", b"2").unwrap();
882
883 let results = store.query(&ChildrenAdapter, "/a").unwrap();
884 assert_eq!(results.len(), 1);
885 match &results[0] {
886 QueryResult::Keys(keys) => {
887 assert!(keys.contains(&"/a/b".to_string()));
888 assert!(keys.contains(&"/a/c".to_string()));
889 }
890 _ => panic!("Expected Keys result"),
891 }
892 }
893
894 #[test]
895 fn test_query_adapter_object_safe() {
896 struct CountAdapter;
898 impl QueryAdapter for CountAdapter {
899 fn execute(&self, store: &Store, query: &str) -> Result<Vec<QueryResult>, StoreError> {
900 let keys = store.list(query)?;
901 Ok(vec![QueryResult::Count(keys.len())])
902 }
903 }
904
905 let adapter: Box<dyn QueryAdapter> = Box::new(CountAdapter);
906 let store = Store::new();
907 store.put("/x", b"x").unwrap();
908 store.put("/y", b"y").unwrap();
909
910 let results = store.query(&*adapter, "/").unwrap();
911 match &results[0] {
912 QueryResult::Count(n) => assert_eq!(*n, 2),
913 _ => panic!("Expected Count result"),
914 }
915 }
916
917 #[test]
918 fn test_nearest_semantic() {
919 use g_math::fixed_point::FixedPoint;
920
921 let store = Store::new();
922 store.put("/courses/trauma/emdr", b"EMDR").unwrap();
923 store.put("/courses/trauma/ptss", b"PTSS").unwrap();
924 store.put("/courses/cgt/basis", b"CGT").unwrap();
925
926 let coords = |d0: f64, d1: f64| -> Vec<u8> {
928 let mut v = vec![0u8; 2 * 16];
929 v[0..16].copy_from_slice(&FixedPoint::from_f64(d0).raw().to_le_bytes());
930 v[16..32].copy_from_slice(&FixedPoint::from_f64(d1).raw().to_le_bytes());
931 v
932 };
933
934 store.set_semantic("/courses/trauma/emdr", coords(0.9, 0.1)).unwrap();
935 store.set_semantic("/courses/trauma/ptss", coords(0.8, 0.2)).unwrap();
936 store.set_semantic("/courses/cgt/basis", coords(0.1, 0.9)).unwrap();
937
938 let query = coords(0.85, 0.15);
940 let results = store.nearest_semantic(&query, 3, 0..2).unwrap();
941
942 assert_eq!(results.len(), 3);
943 let paths: Vec<&str> = results.iter().map(|(p, _)| p.as_str()).collect();
945 assert!(paths[0].contains("trauma"), "Nearest should be a trauma course, got {}", paths[0]);
946 assert!(paths[2].contains("cgt"), "Farthest should be CGT, got {}", paths[2]);
947 }
948
949 #[test]
950 fn test_neighbors_semantic() {
951 use g_math::fixed_point::FixedPoint;
952
953 let store = Store::new();
954 store.put("/a", b"a").unwrap();
955 store.put("/b", b"b").unwrap();
956 store.put("/c", b"c").unwrap();
957
958 let coords = |v: f64| -> Vec<u8> {
959 let mut buf = vec![0u8; 16];
960 buf[0..16].copy_from_slice(&FixedPoint::from_f64(v).raw().to_le_bytes());
961 buf
962 };
963
964 store.set_semantic("/a", coords(0.1)).unwrap();
965 store.set_semantic("/b", coords(0.2)).unwrap();
966 store.set_semantic("/c", coords(0.9)).unwrap();
967
968 let results = store.neighbors_semantic("/a", 2, 0..1).unwrap();
970 assert_eq!(results.len(), 2);
971 assert_eq!(results[0].0, "/b", "Nearest semantic neighbor of /a should be /b");
972 assert_eq!(results[1].0, "/c", "Second neighbor of /a should be /c");
973
974 let paths: Vec<&str> = results.iter().map(|(p, _)| p.as_str()).collect();
976 assert!(!paths.contains(&"/a"), "Self should be excluded from neighbors_semantic");
977 }
978
979 #[test]
980 fn test_semantic_distance_utility() {
981 use g_math::fixed_point::FixedPoint;
982
983 let coords = |d0: f64, d1: f64| -> Vec<u8> {
984 let mut v = vec![0u8; 2 * 16];
985 v[0..16].copy_from_slice(&FixedPoint::from_f64(d0).raw().to_le_bytes());
986 v[16..32].copy_from_slice(&FixedPoint::from_f64(d1).raw().to_le_bytes());
987 v
988 };
989
990 let a = coords(0.0, 0.0);
991 let b = coords(0.3, 0.4);
992
993 let dist = Store::semantic_distance(&a, &b, 0..2);
995 assert!((dist.to_f64() - 0.5).abs() < 0.01,
996 "Distance (0,0)→(0.3,0.4) should be 0.5, got {}", dist.to_f64());
997 }
998}