1use std::time::Instant;
4
5use crate::activation::*;
6use crate::counterfactual::*;
7use crate::error::M1ndResult;
8use crate::graph::Graph;
9use crate::plasticity::*;
10use crate::resonance::*;
11use crate::seed::SeedFinder;
12use crate::semantic::*;
13use crate::temporal::*;
14use crate::topology::*;
15use crate::types::*;
16use crate::xlr::*;
17
18#[derive(Clone, Debug)]
25pub struct QueryConfig {
26 pub query: String,
27 pub agent_id: String,
28 pub top_k: usize,
29 pub dimensions: Vec<Dimension>,
30 pub xlr_enabled: bool,
31 pub include_ghost_edges: bool,
32 pub include_structural_holes: bool,
33 pub propagation: PropagationConfig,
34}
35
36impl Default for QueryConfig {
37 fn default() -> Self {
38 Self {
39 query: String::new(),
40 agent_id: String::new(),
41 top_k: 20,
42 dimensions: vec![
43 Dimension::Structural,
44 Dimension::Semantic,
45 Dimension::Temporal,
46 Dimension::Causal,
47 ],
48 xlr_enabled: true,
49 include_ghost_edges: true,
50 include_structural_holes: false,
51 propagation: PropagationConfig::default(),
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
63pub struct GhostEdge {
64 pub source: NodeId,
65 pub target: NodeId,
66 pub shared_dimensions: Vec<Dimension>,
67 pub strength: FiniteF32,
68}
69
70#[derive(Clone, Debug)]
77pub struct StructuralHole {
78 pub node: NodeId,
79 pub sibling_avg_activation: FiniteF32,
80 pub reason: String,
81}
82
83#[derive(Clone, Debug)]
89pub struct QueryResult {
90 pub activation: ActivationResult,
91 pub ghost_edges: Vec<GhostEdge>,
92 pub structural_holes: Vec<StructuralHole>,
93 pub plasticity: PlasticityResult,
94 pub elapsed_ms: f64,
95}
96
97pub struct QueryOrchestrator {
105 pub engine: HybridEngine,
106 pub xlr: AdaptiveXlrEngine,
107 pub semantic: SemanticEngine,
108 pub temporal: TemporalEngine,
109 pub topology: TopologyAnalyzer,
110 pub resonance: ResonanceEngine,
111 pub plasticity: PlasticityEngine,
112 pub counterfactual: CounterfactualEngine,
113}
114
115impl QueryOrchestrator {
116 pub fn build(graph: &Graph) -> M1ndResult<Self> {
119 let engine = HybridEngine::new();
120 let xlr = AdaptiveXlrEngine::with_defaults();
121 let semantic = SemanticEngine::build(graph, SemanticWeights::default())?;
122 let temporal = TemporalEngine::build(graph)?;
123 let topology = TopologyAnalyzer::with_defaults();
124 let resonance = ResonanceEngine::with_defaults();
125 let plasticity = PlasticityEngine::new(graph, PlasticityConfig::default());
126 let counterfactual = CounterfactualEngine::with_defaults();
127
128 Ok(Self {
129 engine,
130 xlr,
131 semantic,
132 temporal,
133 topology,
134 resonance,
135 plasticity,
136 counterfactual,
137 })
138 }
139
140 pub fn query(&mut self, graph: &mut Graph, config: &QueryConfig) -> M1ndResult<QueryResult> {
145 let start = Instant::now();
146
147 let seeds = SeedFinder::find_seeds(graph, &config.query, config.top_k * 5)?;
149
150 if seeds.is_empty() {
151 return Ok(QueryResult {
152 activation: ActivationResult {
153 activated: Vec::new(),
154 seeds: Vec::new(),
155 elapsed_ns: 0,
156 xlr_fallback_used: false,
157 },
158 ghost_edges: Vec::new(),
159 structural_holes: Vec::new(),
160 plasticity: PlasticityResult {
161 edges_strengthened: 0,
162 edges_decayed: 0,
163 ltp_events: 0,
164 ltd_events: 0,
165 homeostatic_rescales: 0,
166 priming_nodes: 0,
167 },
168 elapsed_ms: start.elapsed().as_secs_f64() * 1000.0,
169 });
170 }
171
172 let d1 = self.engine.propagate(graph, &seeds, &config.propagation)?;
175
176 let d2 = activate_semantic(graph, &self.semantic, &config.query, config.top_k)?;
178
179 let d3 = activate_temporal(graph, &seeds, &TemporalWeights::default())?;
181
182 let d4 = activate_causal(graph, &seeds, &config.propagation)?;
184
185 let mut xlr_fallback = false;
187 let d1_final = if config.xlr_enabled {
188 let xlr_result = self.xlr.query(graph, &seeds, &config.propagation)?;
189 xlr_fallback = xlr_result.fallback_to_hot_only;
190
191 if !xlr_result.activations.is_empty() {
193 DimensionResult {
194 scores: xlr_result.activations,
195 dimension: Dimension::Structural,
196 elapsed_ns: d1.elapsed_ns,
197 }
198 } else {
199 d1
200 }
201 } else {
202 d1
203 };
204
205 let results = [d1_final, d2, d3, d4];
207 let mut activation = merge_dimensions(&results, config.top_k)?;
208 activation.seeds = seeds.clone();
209 activation.xlr_fallback_used = xlr_fallback;
210
211 for node in &mut activation.activated {
213 let idx = node.node.as_usize();
214 if idx < graph.nodes.pagerank.len() {
215 let pr_boost = graph.nodes.pagerank[idx].get() * 0.1;
216 node.activation = FiniteF32::new(node.activation.get() + pr_boost);
217 }
218 }
219 activation
221 .activated
222 .sort_by(|a, b| b.activation.cmp(&a.activation));
223
224 let ghost_edges = if config.include_ghost_edges {
226 self.detect_ghost_edges(graph, &activation)?
227 } else {
228 Vec::new()
229 };
230
231 let structural_holes = if config.include_structural_holes {
233 self.detect_structural_holes(graph, &activation, FiniteF32::new(0.3))?
234 } else {
235 Vec::new()
236 };
237
238 let activated_pairs: Vec<(NodeId, FiniteF32)> = activation
240 .activated
241 .iter()
242 .map(|a| (a.node, a.activation))
243 .collect();
244 let plasticity_result =
245 self.plasticity
246 .update(graph, &activated_pairs, &seeds, &config.query)?;
247
248 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
249
250 Ok(QueryResult {
251 activation,
252 ghost_edges,
253 structural_holes,
254 plasticity: plasticity_result,
255 elapsed_ms,
256 })
257 }
258
259 pub fn detect_ghost_edges(
263 &self,
264 graph: &Graph,
265 activation: &ActivationResult,
266 ) -> M1ndResult<Vec<GhostEdge>> {
267 let mut ghosts = Vec::new();
268 let n = graph.num_nodes() as usize;
269
270 let activated: Vec<&ActivatedNode> = activation
272 .activated
273 .iter()
274 .filter(|a| a.active_dimension_count >= 2)
275 .take(50) .collect();
277
278 for i in 0..activated.len() {
279 for j in (i + 1)..activated.len() {
280 let a = activated[i];
281 let b = activated[j];
282
283 let range = graph.csr.out_range(a.node);
285 let connected = range.into_iter().any(|k| graph.csr.targets[k] == b.node);
286
287 if !connected {
288 let mut shared = Vec::new();
290 let dims = [
291 Dimension::Structural,
292 Dimension::Semantic,
293 Dimension::Temporal,
294 Dimension::Causal,
295 ];
296 for (d, dim) in dims.iter().enumerate() {
297 if a.dimensions[d].get() > 0.01 && b.dimensions[d].get() > 0.01 {
298 shared.push(*dim);
299 }
300 }
301
302 if shared.len() >= 2 {
303 let strength = FiniteF32::new(
304 (a.activation.get() * b.activation.get()).sqrt().min(1.0),
305 );
306 ghosts.push(GhostEdge {
307 source: a.node,
308 target: b.node,
309 shared_dimensions: shared,
310 strength,
311 });
312 }
313 }
314 }
315 }
316
317 ghosts.sort_by(|a, b| b.strength.cmp(&a.strength));
318 ghosts.truncate(10);
319 Ok(ghosts)
320 }
321
322 pub fn detect_structural_holes(
325 &self,
326 graph: &Graph,
327 activation: &ActivationResult,
328 min_sibling_activation: FiniteF32,
329 ) -> M1ndResult<Vec<StructuralHole>> {
330 let n = graph.num_nodes() as usize;
331 let mut holes = Vec::new();
332
333 let mut act_map = vec![0.0f32; n];
335 for a in &activation.activated {
336 let idx = a.node.as_usize();
337 if idx < n {
338 act_map[idx] = a.activation.get();
339 }
340 }
341
342 for i in 0..n {
344 if act_map[i] > 0.01 {
345 continue; }
347
348 let range = graph.csr.out_range(NodeId::new(i as u32));
349 let degree = (range.end - range.start) as f32;
350 if degree == 0.0 {
351 continue;
352 }
353
354 let mut neighbor_act_sum = 0.0f32;
355 let mut activated_neighbors = 0u32;
356
357 for j in range {
358 let tgt = graph.csr.targets[j].as_usize();
359 if tgt < n && act_map[tgt] > min_sibling_activation.get() {
360 neighbor_act_sum += act_map[tgt];
361 activated_neighbors += 1;
362 }
363 }
364
365 if activated_neighbors >= 2 {
366 let avg = neighbor_act_sum / activated_neighbors as f32;
367 holes.push(StructuralHole {
368 node: NodeId::new(i as u32),
369 sibling_avg_activation: FiniteF32::new(avg),
370 reason: format!(
371 "{} activated neighbors (avg={:.2}) but node inactive",
372 activated_neighbors, avg
373 ),
374 });
375 }
376 }
377
378 holes.sort_by(|a, b| b.sibling_avg_activation.cmp(&a.sibling_avg_activation));
379 holes.truncate(10);
380 Ok(holes)
381 }
382}
383
384const _: () = {
387 fn _use_imports() {
388 let _ = std::mem::size_of::<XlrResult>();
389 let _ = std::mem::size_of::<TemporalReport>();
390 let _ = std::mem::size_of::<TopologyReport>();
391 let _ = std::mem::size_of::<ResonanceReport>();
392 let _ = std::mem::size_of::<CounterfactualResult>();
393 }
394};