pub struct Store { /* private fields */ }Expand description
A simple, ergonomic hierarchical data store backed by Hyperbolic Tree Tensors.
All keys are path-like strings (e.g. "/users/alice"). Parent directories
are created automatically. The root node "/" always exists.
§Examples
use horon_engine::Store;
use g_math::fixed_point::FixedPoint;
let store = Store::new();
// Store and retrieve data
store.put("/config/db", b"postgres://localhost").unwrap();
assert_eq!(store.get("/config/db").unwrap(), b"postgres://localhost");
// Coordinates and distances are Q64.64 fixed point, never floats: the
// same query returns bit-identical results on any platform. Converting
// from a decimal literal is explicit, so the lossy step is visible at
// the call site rather than hidden inside the API.
let origin: Vec<FixedPoint> = (0..4).map(|_| FixedPoint::from_int(0)).collect();
let (path, distance) = store.nearest(&origin).unwrap();Implementations§
Source§impl Store
impl Store
Sourcepub fn with_config(config: StoreConfig) -> Self
pub fn with_config(config: StoreConfig) -> Self
Create a new Store with custom configuration.
Sourcepub fn put(&self, key: &str, data: &[u8]) -> Result<(), StoreError>
pub fn put(&self, key: &str, data: &[u8]) -> Result<(), StoreError>
Store data at a key (upsert — inserts or updates).
Parent directories are created automatically.
Sourcepub fn put_data_only(&self, key: &str, data: &[u8]) -> Result<(), StoreError>
pub fn put_data_only(&self, key: &str, data: &[u8]) -> Result<(), StoreError>
Store data without geometric embedding (data + semantic only).
Much faster than put() for bulk loading — skips Sarkar embedding,
VP-tree, and power diagram construction. Semantic queries
(nearest_semantic, neighbors_semantic, get_semantic) work
normally. Spatial queries (nearest, neighbors) will not find
nodes loaded this way.
Sourcepub fn put_positioned(
&self,
key: &str,
data: &[u8],
child_index: u32,
) -> Result<(), StoreError>
pub fn put_positioned( &self, key: &str, data: &[u8], child_index: u32, ) -> Result<(), StoreError>
Store data with an explicit child_index for deterministic Sarkar reconstruction.
Used during snapshot replay: the stored child_index ensures the node gets the same geometric position regardless of replay order.
Sourcepub fn children(&self, path: &str) -> Result<Vec<String>, StoreError>
pub fn children(&self, path: &str) -> Result<Vec<String>, StoreError>
List immediate children of a path.
Returns only direct children, not the full subtree.
Sourcepub fn list(&self, prefix: &str) -> Result<Vec<String>, StoreError>
pub fn list(&self, prefix: &str) -> Result<Vec<String>, StoreError>
List all keys under a prefix (full subtree).
Sourcepub fn set_meta(
&self,
key: &str,
name: &str,
value: &str,
) -> Result<(), StoreError>
pub fn set_meta( &self, key: &str, name: &str, value: &str, ) -> Result<(), StoreError>
Set a metadata field on a key.
Sourcepub fn get_meta(&self, key: &str) -> Result<HashMap<String, String>, StoreError>
pub fn get_meta(&self, key: &str) -> Result<HashMap<String, String>, StoreError>
Get all metadata for a key.
Sourcepub fn set_semantic(&self, key: &str, coords: Vec<u8>) -> Result<(), StoreError>
pub fn set_semantic(&self, key: &str, coords: Vec<u8>) -> Result<(), StoreError>
Set semantic coordinates on a key (raw Q64.64 bytes, 16 bytes per dimension).
Each coordinate is 16 bytes (i128 LE). For example, 2 semantic dimensions
requires 32 bytes. Use FixedPoint::from_f64(value).raw().to_le_bytes()
to encode each coordinate.
Returns InvalidOperation if coords is not a multiple of 16 bytes —
a misaligned vector would otherwise have its trailing partial dimension
silently ignored by distance computations.
Sourcepub fn get_semantic(&self, key: &str) -> Result<Vec<u8>, StoreError>
pub fn get_semantic(&self, key: &str) -> Result<Vec<u8>, StoreError>
Get semantic coordinates for a key (raw Q64.64 bytes).
Returns empty Vec if no semantic coordinates have been set.
Sourcepub fn max_depth(&self) -> u32
pub fn max_depth(&self) -> u32
The deepest node this store can place, given its tau.
A node sits at hyperbolic radius depth × tau, and Q64.64 stops
representing coordinate differences faithfully past a radius of about
21 — beyond that every node saturates to the same distance and ranking
becomes arbitrary. Placement past the limit is refused, so this is
the number to design against rather than discover by failing.
Raising tau for wider fan-out lowers this proportionally: the default
tau = 1.0 gives 21, tau = 2.0 gives 10.
Full metric fidelity degrades before the hard limit — the step error
along a geodesic is 3.4e-8 at radius 16, 2.0e-4 at 20. See
docs/ARCHITECTURE.md.
Sourcepub fn nearest(
&self,
coords: &[FixedPoint],
) -> Result<(String, FixedPoint), StoreError>
pub fn nearest( &self, coords: &[FixedPoint], ) -> Result<(String, FixedPoint), StoreError>
Find the nearest stored node to an arbitrary point in hyperbolic space.
Coordinates are in the Poincare disk model (each component in (-1, 1)).
The answer is the true nearest node: every surviving candidate is
ranked by exact hyperbolic distance.
Cost: the query’s own cell, then rings outward until a proven lower bound rules out every cell not yet visited. Exact — nothing is capped, sampled or windowed. How many cells that takes depends on how the tree is shaped, so this is not O(1); measured figures are in BENCHMARKS.md.
Returns (key, hyperbolic_distance).
Sourcepub fn nearest_k(
&self,
coords: &[FixedPoint],
k: usize,
) -> Result<Vec<(String, FixedPoint)>, StoreError>
pub fn nearest_k( &self, coords: &[FixedPoint], k: usize, ) -> Result<Vec<(String, FixedPoint)>, StoreError>
Find the k nearest stored nodes to an arbitrary point in hyperbolic space.
Like nearest() but returns multiple candidates, enabling the caller
to post-filter and still get results.
Cost: as nearest() — the query’s own cell, then rings outward until a proven lower
bound rules out every cell not yet visited. Exact — nothing is capped,
sampled or windowed. How many cells that takes depends on how the tree
is shaped, so this is not O(1); measured figures are in BENCHMARKS.md.
Returns (key, hyperbolic_distance) sorted by ascending distance.
Sourcepub fn neighbors(&self, path: &str, k: usize) -> Result<Vec<String>, StoreError>
pub fn neighbors(&self, path: &str, k: usize) -> Result<Vec<String>, StoreError>
Find the k nearest neighbors of an existing node.
Returns keys sorted by ascending hyperbolic distance. The queried key itself is excluded from results.
Cost: as nearest_k(), from the queried node’s own position.
Sourcepub fn nearest_semantic(
&self,
query_coords: &[u8],
k: usize,
dim_range: Range<usize>,
) -> Result<Vec<(String, FixedPoint)>, StoreError>
pub fn nearest_semantic( &self, query_coords: &[u8], k: usize, dim_range: Range<usize>, ) -> Result<Vec<(String, FixedPoint)>, StoreError>
Find the k nearest nodes by Euclidean distance across a dimensional slice.
A dimensional slice selects which semantic dimensions to compare.
For example, 16..33 compares only category preference axes,
ignoring operational dimensions. Different slices answer different
questions from the same data.
query_coords: raw Q64.64 bytes (16 bytes per dimension).
k: number of nearest neighbors to return.
dim_range: which dimensions to include in the distance calculation.
Returns (key, distance) sorted ascending by (distance, key) —
ties break deterministically.
Returns InvalidOperation if query_coords is not a multiple of 16 bytes.
Complexity (docs/SEMANTIC_INDEX.md): stores below
SEMANTIC_INDEX_MIN_NODES use a brute-force O(n × d) scan. Larger
stores use a lazily built per-dim_range VP-tree: O(log n) expected
per warm query on low-dimensional slices; the first query for a slice
after any semantic write pays an O(n log n) rebuild. Results are
identical on both paths.
Sourcepub fn neighbors_semantic(
&self,
path: &str,
k: usize,
dim_range: Range<usize>,
) -> Result<Vec<(String, FixedPoint)>, StoreError>
pub fn neighbors_semantic( &self, path: &str, k: usize, dim_range: Range<usize>, ) -> Result<Vec<(String, FixedPoint)>, StoreError>
Find the k nearest nodes to an existing node by semantic dimensional distance.
Reads the node’s semantic coordinates and finds the closest other nodes in the specified dimensional slice. The queried node is excluded.
Complexity: same routing as Store::nearest_semantic (indexed
above the node floor, brute-force below).
Returns keys sorted by ascending semantic distance.
Sourcepub fn find_similar(
&self,
key: &str,
k: usize,
dim_range: Range<usize>,
) -> Result<Vec<(String, FixedPoint)>, StoreError>
pub fn find_similar( &self, key: &str, k: usize, dim_range: Range<usize>, ) -> Result<Vec<(String, FixedPoint)>, StoreError>
Find the k stored nodes most similar to an existing node across a dimensional slice — “what’s like this one?”.
This is Store::neighbors_semantic under a task-shaped name: it
reads the node’s semantic coordinates and returns the k nearest other
nodes by Euclidean distance over dim_range, sorted ascending by
(distance, key). Same routing and cost as nearest_semantic.
Sourcepub fn find_outliers(
&self,
prefix: &str,
z_threshold: FixedPoint,
dim_range: Range<usize>,
) -> Result<Vec<SemanticOutlier>, StoreError>
pub fn find_outliers( &self, prefix: &str, z_threshold: FixedPoint, dim_range: Range<usize>, ) -> Result<Vec<SemanticOutlier>, StoreError>
Find semantic outliers among the nodes under a key prefix: nodes whose average distance to their nearest peers is anomalously large relative to the population.
For each node under prefix that has semantic coordinates, computes
the average distance to its OUTLIER_KNN (10, capped at
population−1) nearest peers within the same population over
dim_range, then flags nodes whose average exceeds the population
mean by more than z_threshold standard deviations. Returns outliers
sorted by descending z-score (ties by key). This is the
“room 403 rates unlike its floor-mates” / “course far from every
peer” query as one call.
Statistics are computed strictly within the prefix population — nodes
outside prefix (or without coordinates) neither appear nor skew the
baseline. Populations below OUTLIER_MIN_POPULATION (5) return no
outliers: z-scores over a handful of nodes are noise, not findings.
Complexity: builds a dedicated VP-tree over the population (O(m log m) distance evaluations) plus one k-NN query per node — ~seconds at 10k nodes, versus the O(m²) pairwise scan this replaces. Deterministic: identical stores produce identical results.
Returns InvalidOperation if z_threshold is not a finite positive
number.
Sourcepub fn embed_existing(&self, key: &str) -> Result<bool, StoreError>
pub fn embed_existing(&self, key: &str) -> Result<bool, StoreError>
Upgrade a data-only key (inserted via Store::put_data_only) to a
full geometric embedding, in place.
Missing ancestors are embedded first; the node’s key, value,
metadata, and semantic coordinates are preserved. After this call the
key participates in spatial queries (nearest, neighbors,
find_within) and has a Store::position.
Returns whether this call performed the upgrade (false = the key
was already embedded; idempotent). Positions are derived state, not
persisted: deterministic for a fixed operation sequence, but a
lazily-loaded store must re-embed after reopening.
Sourcepub fn embed_all(&self, prefix: &str) -> Result<usize, StoreError>
pub fn embed_all(&self, prefix: &str) -> Result<usize, StoreError>
Embed the prefix node (when it exists) and every data-only key under it (convenience). Parents embed before children (sorted order + ancestor recursion). Returns how many keys this call upgraded.
Sourcepub fn position(&self, key: &str) -> Result<Vec<FixedPoint>, StoreError>
pub fn position(&self, key: &str) -> Result<Vec<FixedPoint>, StoreError>
The hyperbolic (Poincaré) position of a stored key.
Errors for unknown keys and for data-only nodes (no embedding).
Sourcepub fn semantic_epoch(&self) -> u64
pub fn semantic_epoch(&self) -> u64
Monotone counter of semantic-relevant mutations (coordinate writes,
inserts, deletes). External caches over semantic state — e.g. the semantic disk
crate::semantic_disk::SemanticDisk — tag their builds with it and
rebuild when it has advanced, exactly like the internal index cache.
Sourcepub fn semantic_distance(
coords_a: &[u8],
coords_b: &[u8],
dim_range: Range<usize>,
) -> FixedPoint
pub fn semantic_distance( coords_a: &[u8], coords_b: &[u8], dim_range: Range<usize>, ) -> FixedPoint
Compute the Euclidean distance between two raw semantic coordinate vectors across a dimensional slice.
Utility method for computing distances without querying the store.
Sourcepub fn find_within(
&self,
path: &str,
radius: FixedPoint,
) -> Result<Vec<String>, StoreError>
pub fn find_within( &self, path: &str, radius: FixedPoint, ) -> Result<Vec<String>, StoreError>
Find all nodes within a hyperbolic distance of an existing node.
Cost: ring expansion bounded by radius rather than by a running
k-th distance, so it grows with the radius and the result size. A
radius too large to express as a cosh prunes nothing and sweeps the
whole index — slow, but still exact.
Sourcepub fn query(
&self,
adapter: &dyn QueryAdapter,
query: &str,
) -> Result<Vec<QueryResult>, StoreError>
pub fn query( &self, adapter: &dyn QueryAdapter, query: &str, ) -> Result<Vec<QueryResult>, StoreError>
Execute a query using a pluggable adapter.
The adapter receives a reference to this store and the query string,
and returns results by calling get, list, neighbors, etc.
Sourcepub fn inner(&self) -> &HTTStorage
pub fn inner(&self) -> &HTTStorage
Access the underlying HTTStorage for advanced operations.
Sourcepub fn inner_mut(&mut self) -> &mut HTTStorage
👎Deprecated: All HTTStorage methods now take &self; use inner() instead
pub fn inner_mut(&mut self) -> &mut HTTStorage
All HTTStorage methods now take &self; use inner() instead
Mutably access the underlying HTTStorage for advanced operations.