Skip to main content

clawft_kernel/
quantum_backend.rs

1//! Quantum backend abstraction for WeftOS.
2//!
3//! EXPERIMENTAL (0.6.x). Interface only — concrete backends (`quantum_pasqal`,
4//! `quantum_braket`) are stubs until full REST + auth land in a later release.
5//!
6//! The trait is backend-agnostic so that neutral-atom analog processors from
7//! different vendors (Pasqal Fresnel, QuEra Aquila on AWS Braket) can be
8//! swapped or chained for fallback. Shared graph → atom-position mapping lives
9//! in `quantum_register`.
10//!
11//! See `.planning/development_notes/pasqal-integration.md` §6.1 and §13.
12
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16
17use crate::quantum_state::QuantumCognitiveState;
18
19/// Opaque handle to an in-flight quantum job.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct JobHandle {
22    pub backend: &'static str,
23    pub job_id: String,
24    pub batch_id: Option<String>,
25}
26
27/// Measurement results from a quantum backend, normalized across vendors.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct QuantumResults {
30    /// One bitstring per shot. Inner `Vec<u8>` is 0/1 per atom.
31    pub bitstrings: Vec<Vec<u8>>,
32    /// Per-atom Rydberg-excitation probability (length = n_atoms).
33    pub rydberg_probs: Vec<f64>,
34    /// Number of shots that actually completed.
35    pub shots: u32,
36}
37
38/// Job lifecycle state, normalized across vendors.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum JobStatus {
41    Pending,
42    Running,
43    Done,
44    Canceled,
45    Error,
46}
47
48/// Backend health snapshot.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct BackendStatus {
51    pub name: &'static str,
52    pub reachable: bool,
53    pub queue_depth: Option<u32>,
54    pub estimated_wait: Option<Duration>,
55    pub max_qubits: usize,
56}
57
58/// Evolution parameters — the subset of the Rydberg Hamiltonian controls that
59/// both Pasqal and QuEra expose in analog mode.
60#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
61pub struct EvolutionParams {
62    pub duration_ns: u64,
63    pub omega_rad_per_us: f64,
64    pub detuning_rad_per_us: f64,
65    pub phase_rad: f64,
66    pub shots: u32,
67}
68
69impl Default for EvolutionParams {
70    fn default() -> Self {
71        Self {
72            duration_ns: 1000,
73            omega_rad_per_us: 1.0,
74            detuning_rad_per_us: 0.0,
75            phase_rad: 0.0,
76            shots: 100,
77        }
78    }
79}
80
81/// Errors from quantum backends.
82#[derive(Debug, thiserror::Error)]
83pub enum QuantumError {
84    #[error("backend not implemented (experimental 0.6.x interface)")]
85    NotImplemented,
86    #[error("authentication failed: {0}")]
87    Auth(String),
88    #[error("transport error: {0}")]
89    Transport(String),
90    #[error("backend rejected request: {0}")]
91    Rejected(String),
92    #[error("graph too large for backend: {nodes} > {max}")]
93    GraphTooLarge { nodes: usize, max: usize },
94    #[error("invalid register layout: {0}")]
95    InvalidRegister(String),
96    #[error("serialization: {0}")]
97    Serde(String),
98}
99
100/// Abstraction over neutral-atom analog quantum processors.
101///
102/// All implementations MUST be object-safe so backends can be stored as
103/// `Box<dyn QuantumBackend>` for runtime selection / fallback chaining.
104#[async_trait]
105pub trait QuantumBackend: Send + Sync {
106    fn name(&self) -> &'static str;
107
108    fn max_qubits(&self) -> usize;
109
110    async fn health_check(&self) -> Result<BackendStatus, QuantumError>;
111
112    /// Submit an evolution of the given quantum state on the given register.
113    /// Register coordinates are in micrometers.
114    async fn submit_evolution(
115        &self,
116        register: &[(String, [f64; 2])],
117        state: &QuantumCognitiveState,
118        params: EvolutionParams,
119    ) -> Result<JobHandle, QuantumError>;
120
121    async fn poll(&self, handle: &JobHandle) -> Result<JobStatus, QuantumError>;
122
123    async fn get_results(&self, handle: &JobHandle)
124    -> Result<Option<QuantumResults>, QuantumError>;
125
126    async fn cancel(&self, handle: &JobHandle) -> Result<(), QuantumError>;
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    fn _assert_object_safe(_: &dyn QuantumBackend) {}
134
135    #[test]
136    fn evolution_params_default_sane() {
137        let p = EvolutionParams::default();
138        assert!(p.shots > 0);
139        assert!(p.duration_ns > 0);
140    }
141
142    #[test]
143    fn not_implemented_error_formats() {
144        let e = QuantumError::NotImplemented;
145        assert!(e.to_string().contains("not implemented"));
146    }
147
148    #[test]
149    fn graph_too_large_error_contains_counts() {
150        let e = QuantumError::GraphTooLarge {
151            nodes: 500,
152            max: 100,
153        };
154        let msg = e.to_string();
155        assert!(msg.contains("500"));
156        assert!(msg.contains("100"));
157    }
158}