1use 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#[repr(C)]
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct QuboProblem {
18 pub problem_id: u64,
20 pub coefficients: [(usize, usize, f32); MAX_QUBO_COEFFICIENTS],
22 pub coefficient_count: usize,
24 pub linear_terms: [(usize, f32); MAX_LINEAR_TERMS],
26 pub linear_term_count: usize,
28 pub size: usize,
30 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#[repr(C)]
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub struct GsrResult {
74 pub problem_id: u64,
76 pub winning_context: f32,
78 pub confidence: f32,
80 pub resolved_at: u64,
82 pub compute_time_ms: u64,
84 pub classical_fallback: bool,
86}
87
88#[repr(C)]
90#[derive(Debug, Clone, Copy, PartialEq)]
91pub struct GsrRequest {
92 pub problem: QuboProblem,
94 pub max_compute_time_ms: u64,
96 pub priority: u8,
98 pub requested_at: u64,
100}
101
102pub 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 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 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 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 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 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 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 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 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 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 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 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#[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}