holographic_memory/core/engine/
query.rs1use super::HmsCore;
5use crate::core::entangled::EntangledHVec;
6use crate::core::hopfield;
7use crate::core::types::{IndexStatus, QueryExplanation, RetrievalResult, StorageHealth};
8use rayon::prelude::*;
9
10impl HmsCore {
11 pub fn query(&self, query_vec: &EntangledHVec, k: u32) -> Vec<RetrievalResult> {
13 if query_vec.dim != self.dimensions || k == 0 {
14 return Vec::new();
15 }
16 if self
21 .documents
22 .try_read()
23 .is_some_and(|store| store.pending_vector_count() > 0)
24 {
25 if let Err(error) = self.encode_pending_documents() {
26 tracing::warn!(%error, "deferred document encoding failed; pending chunks stay outside this query");
27 }
28 }
29
30 let _transaction = self.mutation_gate.read();
31 self.shards
32 .read()
33 .query(query_vec, k.min(10000), self.dimensions)
34 }
35
36 #[cfg(feature = "provenance")]
38 pub fn agent_registry(
39 &self,
40 ) -> &parking_lot::RwLock<crate::core::provenance::access::AgentRegistry> {
41 &self.agents
42 }
43
44 #[cfg(feature = "provenance")]
47 pub fn query_as(
48 &self,
49 agent_did: &str,
50 query_vec: &EntangledHVec,
51 k: u32,
52 ) -> anyhow::Result<Vec<RetrievalResult>> {
53 self.agents
54 .read()
55 .authorize(agent_did, crate::core::provenance::access::Permission::Read)?;
56 Ok(self.query(query_vec, k))
57 }
58
59 pub fn explain_query(&self, query_vec: &EntangledHVec, k: u32) -> QueryExplanation {
60 let mut explanation = self
61 .shards
62 .read()
63 .explain_query(query_vec, k, self.dimensions);
64 let (pending, lexical) = self
65 .documents
66 .try_read()
67 .map(|store| (store.pending_vector_count(), store.lexical_vector_count()))
68 .unwrap_or((0, 0));
69 if pending > 0 || lexical > 0 {
70 explanation.rationale.push_str(&format!(
71 " {pending} document chunk(s) await deferred encoding (a query, \
72 encode_pending_documents or maintain_indices installs them); {lexical} \
73 lexical-only chunk(s) are outside vector search."
74 ));
75 }
76 explanation
77 }
78
79 pub fn index_status(&self) -> IndexStatus {
80 let count = self.vector_count().min(u32::MAX as u64) as u32;
81 let nsg = self.nsg_trained();
82 let ivf = self.ivf_trained();
83 let nsg_recommended = !nsg
84 && count > 0
85 && self.config.nsg.auto_threshold > 0
86 && count as usize >= self.config.nsg.auto_threshold;
87 let ivf_recommended = self.config.ivf.enabled
88 && !ivf
89 && count > 0
90 && self.config.ivf.auto_threshold > 0
91 && count as usize >= self.config.ivf.auto_threshold;
92 let recommendation = if nsg_recommended && ivf_recommended {
93 "train NSG and IVF indices"
94 } else if nsg_recommended {
95 "train the NSG index"
96 } else if ivf_recommended {
97 "train the IVF index"
98 } else {
99 "indices are current for the configured thresholds"
100 };
101 IndexStatus {
102 vector_count: count,
103 nsg_trained: nsg,
104 ivf_trained: ivf,
105 nsg_training_recommended: nsg_recommended,
106 ivf_training_recommended: ivf_recommended,
107 recommendation: recommendation.to_string(),
108 }
109 }
110
111 pub fn storage_health(&self) -> StorageHealth {
112 let stats = self.arena.stats();
113 StorageHealth {
114 format_version: stats.format_version,
115 segment_count: stats.segment_count.min(u32::MAX as usize) as u32,
116 used_bytes: stats.used_bytes as f64,
117 capacity_bytes: stats.capacity_bytes as f64,
118 }
119 }
120
121 pub fn maintain_indices(&self) -> anyhow::Result<IndexStatus> {
124 self.maybe_auto_shard(self.vector_count())?;
125 self.encode_pending_documents()?;
126 let before = self.index_status();
127 if before.ivf_training_recommended {
128 self.train_ivf()?;
129 }
130 if before.nsg_training_recommended {
131 self.train_nsg()?;
132 }
133 let document_training_recommended = self.documents.read().dense_index_recommended();
134 if document_training_recommended {
135 self.train_document_index()?;
136 }
137 self.maintain_vector_index(crate::core::documents::DEFAULT_GRAPH_MIN_ROWS)?;
138 Ok(self.index_status())
139 }
140
141 pub fn maintain_indices_background(
143 self: &std::sync::Arc<Self>,
144 ) -> std::thread::JoinHandle<anyhow::Result<IndexStatus>> {
145 let core = std::sync::Arc::clone(self);
146 std::thread::spawn(move || core.maintain_indices())
147 }
148
149 pub fn flush(&self) -> anyhow::Result<()> {
151 let _transaction = self.mutation_gate.write();
152 self.arena.flush()?;
153 self.persist_indices(&self.shards.read())
154 }
155
156 pub fn query_hopfield(
163 &self,
164 query_vec: &EntangledHVec,
165 max_results: u32,
166 ) -> Vec<RetrievalResult> {
167 let shards = self.shards.read();
168 let patterns = shards.collect_all_patterns();
169 let config = &self.config.hopfield;
170
171 if config.max_iter > 1 {
172 hopfield::hopfield_query_iterative(query_vec, &patterns, config, max_results as usize)
173 } else {
174 hopfield::hopfield_query(query_vec, &patterns, config, max_results as usize)
175 }
176 }
177
178 pub fn query_batch(&self, queries: &[EntangledHVec], k: u32) -> Vec<Vec<RetrievalResult>> {
180 queries.par_iter().map(|q| self.query(q, k)).collect()
181 }
182
183 pub fn analyze_components(&self, vector: &EntangledHVec) -> Vec<RetrievalResult> {
186 let cfg = &self.config.query;
187 let neighbors = self.query(vector, cfg.component_max_neighbors);
188 neighbors
189 .into_iter()
190 .filter(|r| r.similarity > cfg.component_similarity_threshold)
191 .collect()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn diagnostics_report_route_lifecycle_and_storage() -> anyhow::Result<()> {
201 let directory = tempfile::tempdir()?;
202 let hms = HmsCore::new(
203 1024,
204 Some(directory.path().to_string_lossy().into_owned()),
205 None,
206 )?;
207 let query = EntangledHVec::new_deterministic(1024, 1);
208 let explanation = hms.explain_query(&query, 5);
209 assert_eq!(explanation.route, "brute_force");
210 assert_eq!(hms.index_status().vector_count, 0);
211 assert_eq!(
212 hms.storage_health().format_version,
213 crate::core::storage::STORAGE_FORMAT_VERSION
214 );
215 hms.flush()?;
216 Ok(())
217 }
218}