Skip to main content

qualia_core_db/tensor/
gsr.rs

1//! Ground-State Resolver (GSR) integration for quantum context resolution.
2//!
3//! This module keeps runtime state zero-heap by using fixed-capacity arrays for
4//! QUBO terms, request queues, and result caches.
5
6use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
7
8pub const MAX_QUBO_COEFFICIENTS: usize = 64;
9pub const MAX_LINEAR_TERMS: usize = 64;
10pub const MAX_PENDING_REQUESTS: usize = 16;
11pub const MAX_RESULTS_CACHE: usize = 32;
12pub const MAX_AXIOM_CACHE: usize = 32;
13
14/// QUBO problem for quantum context resolution.
15#[repr(C)]
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct QuboProblem {
18    /// Hash-based problem identifier.
19    pub problem_id: u64,
20    /// QUBO matrix coefficients.
21    pub coefficients: [(usize, usize, f32); MAX_QUBO_COEFFICIENTS],
22    /// Number of active quadratic coefficients.
23    pub coefficient_count: usize,
24    /// Linear terms.
25    pub linear_terms: [(usize, f32); MAX_LINEAR_TERMS],
26    /// Number of active linear terms.
27    pub linear_term_count: usize,
28    /// Problem size (number of variables).
29    pub size: usize,
30    /// Context identifier for this problem.
31    pub context_id: u64,
32}
33
34impl Default for QuboProblem {
35    fn default() -> Self {
36        Self {
37            problem_id: 0,
38            coefficients: [(0, 0, 0.0); MAX_QUBO_COEFFICIENTS],
39            coefficient_count: 0,
40            linear_terms: [(0, 0.0); MAX_LINEAR_TERMS],
41            linear_term_count: 0,
42            size: 0,
43            context_id: 0,
44        }
45    }
46}
47
48impl QuboProblem {
49    pub fn add_coefficient(&mut self, i: usize, j: usize, coeff: f32) -> Result<(), GsrError> {
50        if self.coefficient_count >= self.coefficients.len() {
51            return Err(GsrError::CoefficientCapacityExceeded);
52        }
53
54        self.coefficients[self.coefficient_count] = (i, j, coeff);
55        self.coefficient_count += 1;
56        Ok(())
57    }
58
59    pub fn add_linear_term(&mut self, i: usize, coeff: f32) -> Result<(), GsrError> {
60        if self.linear_term_count >= self.linear_terms.len() {
61            return Err(GsrError::LinearTermCapacityExceeded);
62        }
63
64        self.linear_terms[self.linear_term_count] = (i, coeff);
65        self.linear_term_count += 1;
66        Ok(())
67    }
68}
69
70/// GSR resolution result.
71#[repr(C)]
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub struct GsrResult {
74    /// Hash-based problem identifier.
75    pub problem_id: u64,
76    /// Winning context (q value to promote to ground truth).
77    pub winning_context: f32,
78    /// Resolution confidence (0.0 to 1.0).
79    pub confidence: f32,
80    /// Resolution timestamp.
81    pub resolved_at: u64,
82    /// Computation time in milliseconds.
83    pub compute_time_ms: u64,
84    /// Whether classical exhaustion was used.
85    pub classical_fallback: bool,
86}
87
88/// GSR resolution request.
89#[repr(C)]
90#[derive(Debug, Clone, Copy, PartialEq)]
91pub struct GsrRequest {
92    /// QUBO problem to solve.
93    pub problem: QuboProblem,
94    /// Maximum computation time in milliseconds.
95    pub max_compute_time_ms: u64,
96    /// Priority level (0 = highest).
97    pub priority: u8,
98    /// Request timestamp.
99    pub requested_at: u64,
100}
101
102/// Ground-State Resolver for quantum context resolution.
103pub struct GroundStateResolver {
104    pending_requests: [Option<GsrRequest>; MAX_PENDING_REQUESTS],
105    results_cache: [Option<GsrResult>; MAX_RESULTS_CACHE],
106    axiom_cache: [Option<(u64, f32)>; MAX_AXIOM_CACHE],
107    last_cleanup: Instant,
108    qpu_available: bool,
109    classical_solver_enabled: bool,
110}
111
112impl GroundStateResolver {
113    /// Create a new GSR instance.
114    pub fn new() -> Self {
115        Self {
116            pending_requests: [None; MAX_PENDING_REQUESTS],
117            results_cache: [None; MAX_RESULTS_CACHE],
118            axiom_cache: [None; MAX_AXIOM_CACHE],
119            last_cleanup: Instant::now(),
120            qpu_available: false,
121            classical_solver_enabled: true,
122        }
123    }
124
125    /// Submit a QUBO problem for resolution.
126    pub fn submit_problem(&mut self, request: GsrRequest) -> Result<u64, GsrError> {
127        if self.get_result(request.problem.problem_id).is_some() {
128            return Ok(request.problem.problem_id);
129        }
130
131        let slot = self
132            .pending_requests
133            .iter_mut()
134            .find(|entry| entry.is_none())
135            .ok_or(GsrError::PendingQueueFull)?;
136        *slot = Some(request);
137
138        self.process_requests()?;
139        Ok(request.problem.problem_id)
140    }
141
142    /// Get result for a problem.
143    pub fn get_result(&self, problem_id: u64) -> Option<GsrResult> {
144        self.results_cache
145            .iter()
146            .flatten()
147            .find(|result| result.problem_id == problem_id)
148            .copied()
149    }
150
151    /// Process pending requests.
152    fn process_requests(&mut self) -> Result<(), GsrError> {
153        for index in 0..self.pending_requests.len() {
154            let request = match self.pending_requests[index].take() {
155                Some(request) => request,
156                None => continue,
157            };
158
159            let result = if self.qpu_available {
160                self.solve_with_qpu(&request)?
161            } else {
162                self.solve_with_classical(&request)?
163            };
164
165            self.store_result(result)?;
166        }
167
168        Ok(())
169    }
170
171    /// Solve problem using QPU semantics.
172    fn solve_with_qpu(&self, request: &GsrRequest) -> Result<GsrResult, GsrError> {
173        let start = Instant::now();
174        let mut winning_context = 0.0f32;
175        let mut saw_term = false;
176
177        for &(_, weight) in request.problem.linear_terms[..request.problem.linear_term_count].iter()
178        {
179            let magnitude = weight.abs();
180            if !saw_term || magnitude > winning_context {
181                winning_context = magnitude;
182                saw_term = true;
183            }
184        }
185
186        Ok(GsrResult {
187            problem_id: request.problem.problem_id,
188            winning_context,
189            confidence: 0.95,
190            resolved_at: current_unix_secs(),
191            compute_time_ms: start.elapsed().as_millis() as u64,
192            classical_fallback: false,
193        })
194    }
195
196    /// Solve problem using classical exhaustion (fallback).
197    fn solve_with_classical(&self, request: &GsrRequest) -> Result<GsrResult, GsrError> {
198        if !self.classical_solver_enabled {
199            return Err(GsrError::ClassicalSolverDisabled);
200        }
201
202        let start = Instant::now();
203        let winning_context = if request.problem.size <= 16 {
204            self.classical_exhaustive_search(&request.problem)?
205        } else {
206            self.classical_greedy_approximation(&request.problem)?
207        };
208
209        Ok(GsrResult {
210            problem_id: request.problem.problem_id,
211            winning_context,
212            confidence: 0.85,
213            resolved_at: current_unix_secs(),
214            compute_time_ms: start.elapsed().as_millis() as u64,
215            classical_fallback: true,
216        })
217    }
218
219    /// Classical exhaustive search for small QUBO problems.
220    fn classical_exhaustive_search(&self, problem: &QuboProblem) -> Result<f32, GsrError> {
221        if problem.size > 31 {
222            return Err(GsrError::ProblemTooLarge);
223        }
224
225        let n = problem.size;
226        let mut best_value = f32::NEG_INFINITY;
227        let mut best_context = 0.0;
228
229        for mask in 0u32..(1u32 << n) {
230            let mut value = 0.0;
231
232            for &(i, j, coeff) in &problem.coefficients[..problem.coefficient_count] {
233                let xi = ((mask >> i) & 1) as f32;
234                let xj = ((mask >> j) & 1) as f32;
235                value += coeff * xi * xj;
236            }
237
238            for &(i, linear_coeff) in &problem.linear_terms[..problem.linear_term_count] {
239                let xi = ((mask >> i) & 1) as f32;
240                value += linear_coeff * xi;
241            }
242
243            if value > best_value {
244                best_value = value;
245                best_context = mask as f32;
246            }
247        }
248
249        Ok(best_context)
250    }
251
252    /// Classical greedy approximation for larger QUBO problems.
253    fn classical_greedy_approximation(&self, problem: &QuboProblem) -> Result<f32, GsrError> {
254        let mut context = 0u32;
255
256        for &(i, linear_coeff) in &problem.linear_terms[..problem.linear_term_count] {
257            if linear_coeff > 0.0 {
258                context |= 1u32 << i;
259            }
260        }
261
262        Ok(context as f32)
263    }
264
265    pub fn set_qpu_available(&mut self, available: bool) {
266        self.qpu_available = available;
267    }
268
269    pub fn is_qpu_available(&self) -> bool {
270        self.qpu_available
271    }
272
273    pub fn set_classical_solver_enabled(&mut self, enabled: bool) {
274        self.classical_solver_enabled = enabled;
275    }
276
277    /// Evolve epistemic frame based on GSR result.
278    pub fn evolve_epistemic_frame(
279        &mut self,
280        problem_id: u64,
281        result: &GsrResult,
282    ) -> Result<(), GsrError> {
283        if let Some(entry) = self
284            .axiom_cache
285            .iter_mut()
286            .find(|entry| matches!(entry, Some((id, _)) if *id == problem_id))
287        {
288            *entry = Some((problem_id, result.winning_context));
289            return Ok(());
290        }
291
292        let slot = self
293            .axiom_cache
294            .iter_mut()
295            .find(|entry| entry.is_none())
296            .ok_or(GsrError::AxiomCacheFull)?;
297        *slot = Some((problem_id, result.winning_context));
298        Ok(())
299    }
300
301    /// Get cached axiom for a problem.
302    pub fn get_cached_axiom(&self, problem_id: u64) -> Option<f32> {
303        self.axiom_cache
304            .iter()
305            .flatten()
306            .find(|(id, _)| *id == problem_id)
307            .map(|(_, value)| *value)
308    }
309
310    /// Clean up old cache entries.
311    pub fn cleanup_cache(&mut self, max_age: Duration) -> Result<(), GsrError> {
312        let now = Instant::now();
313        if now.duration_since(self.last_cleanup) <= Duration::from_secs(300) {
314            return Ok(());
315        }
316
317        let current_time = current_unix_secs();
318        for entry in &mut self.results_cache {
319            if let Some(result) = entry {
320                if current_time.saturating_sub(result.resolved_at) >= max_age.as_secs() {
321                    *entry = None;
322                }
323            }
324        }
325
326        self.last_cleanup = now;
327        Ok(())
328    }
329
330    fn store_result(&mut self, result: GsrResult) -> Result<(), GsrError> {
331        if let Some(entry) = self.results_cache.iter_mut().find(
332            |entry| matches!(entry, Some(existing) if existing.problem_id == result.problem_id),
333        ) {
334            *entry = Some(result);
335            return Ok(());
336        }
337
338        let slot = self
339            .results_cache
340            .iter_mut()
341            .find(|entry| entry.is_none())
342            .ok_or(GsrError::ResultsCacheFull)?;
343        *slot = Some(result);
344        Ok(())
345    }
346}
347
348impl Default for GroundStateResolver {
349    fn default() -> Self {
350        Self::new()
351    }
352}
353
354fn current_unix_secs() -> u64 {
355    SystemTime::now()
356        .duration_since(UNIX_EPOCH)
357        .unwrap()
358        .as_secs()
359}
360
361/// GSR-related errors.
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363pub enum GsrError {
364    CoefficientCapacityExceeded,
365    LinearTermCapacityExceeded,
366    PendingQueueFull,
367    ResultsCacheFull,
368    AxiomCacheFull,
369    ProblemTooLarge,
370    ClassicalSolverDisabled,
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn test_gsr_creation() {
379        let gsr = GroundStateResolver::new();
380        assert!(!gsr.is_qpu_available());
381    }
382
383    #[test]
384    fn test_qpu_availability() {
385        let mut gsr = GroundStateResolver::new();
386        gsr.set_qpu_available(true);
387        assert!(gsr.is_qpu_available());
388
389        gsr.set_qpu_available(false);
390        assert!(!gsr.is_qpu_available());
391    }
392
393    #[test]
394    fn test_classical_exhaustive_search() {
395        let gsr = GroundStateResolver::new();
396        let mut problem = QuboProblem {
397            problem_id: 1,
398            size: 2,
399            context_id: 0,
400            ..QuboProblem::default()
401        };
402        problem.add_linear_term(0, 1.0).unwrap();
403        problem.add_linear_term(1, -0.5).unwrap();
404
405        let result = gsr.classical_exhaustive_search(&problem).unwrap();
406        assert!(result >= 0.0);
407    }
408
409    #[test]
410    fn test_classical_greedy_approximation() {
411        let gsr = GroundStateResolver::new();
412        let mut problem = QuboProblem {
413            problem_id: 2,
414            size: 3,
415            context_id: 0,
416            ..QuboProblem::default()
417        };
418        problem.add_linear_term(0, 1.0).unwrap();
419        problem.add_linear_term(1, -0.5).unwrap();
420        problem.add_linear_term(2, 2.0).unwrap();
421
422        let result = gsr.classical_greedy_approximation(&problem).unwrap();
423        assert!(result >= 0.0);
424    }
425
426    #[test]
427    fn test_axiom_caching() {
428        let mut gsr = GroundStateResolver::new();
429        let result = GsrResult {
430            problem_id: 42,
431            winning_context: 42.0,
432            confidence: 0.9,
433            resolved_at: 1000,
434            compute_time_ms: 100,
435            classical_fallback: false,
436        };
437
438        gsr.evolve_epistemic_frame(42, &result).unwrap();
439        let cached = gsr.get_cached_axiom(42);
440
441        assert_eq!(cached, Some(42.0));
442    }
443
444    #[test]
445    fn test_submit_problem_caches_result() {
446        let mut gsr = GroundStateResolver::new();
447        let mut problem = QuboProblem {
448            problem_id: 7,
449            size: 2,
450            context_id: 9,
451            ..QuboProblem::default()
452        };
453        problem.add_linear_term(0, 1.0).unwrap();
454
455        let request = GsrRequest {
456            problem,
457            max_compute_time_ms: 100,
458            priority: 0,
459            requested_at: 1,
460        };
461
462        let problem_id = gsr.submit_problem(request).unwrap();
463        let result = gsr.get_result(problem_id);
464
465        assert_eq!(problem_id, 7);
466        assert!(result.is_some());
467    }
468}