quantrs2_device/
azure_device.rs

1#[cfg(feature = "azure")]
2use serde_json;
3use std::collections::HashMap;
4#[cfg(feature = "azure")]
5use std::sync::Arc;
6#[cfg(feature = "azure")]
7use std::time::Duration;
8
9#[cfg(feature = "azure")]
10use async_trait::async_trait;
11#[cfg(feature = "azure")]
12use quantrs2_circuit::prelude::Circuit;
13#[cfg(feature = "azure")]
14use tokio::sync::RwLock;
15
16#[cfg(feature = "azure")]
17use crate::azure::{AzureCircuitConfig, AzureQuantumClient, AzureTarget};
18use crate::DeviceError;
19use crate::DeviceResult;
20#[cfg(feature = "azure")]
21use crate::{CircuitExecutor, CircuitResult, QuantumDevice};
22
23/// Configuration for an Azure Quantum device
24#[derive(Debug, Clone)]
25pub struct AzureDeviceConfig {
26    /// Provider ID (e.g., "ionq", "microsoft", "quantinuum")
27    pub provider_id: String,
28    /// Number of shots to run for each circuit
29    pub default_shots: usize,
30    /// Provider-specific parameters
31    #[cfg(feature = "azure")]
32    pub provider_parameters: HashMap<String, serde_json::Value>,
33    #[cfg(not(feature = "azure"))]
34    pub provider_parameters: HashMap<String, String>,
35    /// Timeout for job completion (in seconds)
36    pub timeout_secs: Option<u64>,
37}
38
39impl Default for AzureDeviceConfig {
40    fn default() -> Self {
41        Self {
42            provider_id: "microsoft".to_string(),
43            default_shots: 1000,
44            #[cfg(feature = "azure")]
45            provider_parameters: HashMap::new(),
46            #[cfg(not(feature = "azure"))]
47            provider_parameters: HashMap::new(),
48            timeout_secs: None,
49        }
50    }
51}
52
53/// Azure Quantum device implementation
54#[cfg(feature = "azure")]
55pub struct AzureQuantumDevice {
56    /// Azure Quantum client
57    client: Arc<AzureQuantumClient>,
58    /// Target device ID
59    target_id: String,
60    /// Provider ID
61    provider_id: String,
62    /// Configuration
63    config: AzureDeviceConfig,
64    /// Cached target information
65    target_cache: Arc<RwLock<Option<AzureTarget>>>,
66}
67
68#[cfg(feature = "azure")]
69impl AzureQuantumDevice {
70    /// Create a new Azure Quantum device instance
71    pub async fn new(
72        client: AzureQuantumClient,
73        target_id: &str,
74        provider_id: Option<&str>,
75        config: Option<AzureDeviceConfig>,
76    ) -> DeviceResult<Self> {
77        let client = Arc::new(client);
78        let target_cache = Arc::new(RwLock::new(None));
79
80        // Get target details to validate and get provider if not specified
81        let target = client.get_target(target_id).await?;
82        let provider_id = match provider_id {
83            Some(id) => id.to_string(),
84            None => target.provider_id.clone(),
85        };
86
87        // Create and cache the target
88        let mut cache = target_cache.write().await;
89        *cache = Some(target);
90
91        let config = config.unwrap_or_default();
92
93        Ok(Self {
94            client,
95            target_id: target_id.to_string(),
96            provider_id,
97            config,
98            target_cache: Arc::clone(&target_cache),
99        })
100    }
101
102    /// Get cached target information, fetching if necessary
103    async fn get_target(&self) -> DeviceResult<AzureTarget> {
104        let cache = self.target_cache.read().await;
105
106        if let Some(target) = cache.clone() {
107            return Ok(target);
108        }
109
110        // Cache miss, need to fetch
111        drop(cache);
112        let target = self.client.get_target(&self.target_id).await?;
113
114        let mut cache = self.target_cache.write().await;
115        *cache = Some(target.clone());
116
117        Ok(target)
118    }
119}
120
121#[cfg(feature = "azure")]
122#[async_trait]
123impl QuantumDevice for AzureQuantumDevice {
124    async fn is_available(&self) -> DeviceResult<bool> {
125        let target = self.get_target().await?;
126        Ok(target.status == "Available")
127    }
128
129    async fn qubit_count(&self) -> DeviceResult<usize> {
130        let target = self.get_target().await?;
131        Ok(target.num_qubits)
132    }
133
134    async fn properties(&self) -> DeviceResult<HashMap<String, String>> {
135        let target = self.get_target().await?;
136
137        // Convert complex JSON properties to string representation
138        let mut properties = HashMap::new();
139        for (key, value) in target.properties {
140            properties.insert(key, value.to_string());
141        }
142
143        Ok(properties)
144    }
145
146    async fn is_simulator(&self) -> DeviceResult<bool> {
147        let target = self.get_target().await?;
148        Ok(target.is_simulator)
149    }
150}
151
152#[cfg(feature = "azure")]
153#[async_trait]
154impl CircuitExecutor for AzureQuantumDevice {
155    async fn execute_circuit<const N: usize>(
156        &self,
157        circuit: &Circuit<N>,
158        shots: usize,
159    ) -> DeviceResult<CircuitResult> {
160        // Check if circuit can be executed
161        if !self.can_execute_circuit(circuit).await? {
162            return Err(DeviceError::CircuitConversion(
163                "Circuit cannot be executed on this device".to_string(),
164            ));
165        }
166
167        // Convert circuit to provider-specific format
168        let circuit_str =
169            AzureQuantumClient::circuit_to_provider_format(circuit, &self.provider_id)?;
170
171        // Create config
172        let job_name = format!("quantrs_job_{}", chrono::Utc::now().timestamp());
173        let config = AzureCircuitConfig {
174            name: job_name,
175            circuit: circuit_str,
176            shots: shots.max(1), // Ensure at least 1 shot
177            provider_parameters: self.config.provider_parameters.clone(),
178        };
179
180        // Submit job
181        let job_id = self
182            .client
183            .submit_circuit(&self.target_id, &self.provider_id, config)
184            .await?;
185
186        // Wait for completion
187        let result = self
188            .client
189            .wait_for_job(&job_id, self.config.timeout_secs)
190            .await?;
191
192        // Convert result to CircuitResult
193        let mut counts = HashMap::new();
194        for (bitstring, probability) in result.histogram {
195            // Convert probabilities to counts based on shots
196            let count = (probability * shots as f64).round() as usize;
197            counts.insert(bitstring, count);
198        }
199
200        let mut metadata = HashMap::new();
201        metadata.insert("job_id".to_string(), job_id);
202        metadata.insert("provider".to_string(), self.provider_id.clone());
203        metadata.insert("target".to_string(), self.target_id.clone());
204
205        Ok(CircuitResult {
206            counts,
207            shots,
208            metadata,
209        })
210    }
211
212    async fn execute_circuits<const N: usize>(
213        &self,
214        circuits: Vec<&Circuit<N>>,
215        shots: usize,
216    ) -> DeviceResult<Vec<CircuitResult>> {
217        let mut configs = Vec::with_capacity(circuits.len());
218
219        // Prepare all circuit configs
220        for (idx, circuit) in circuits.iter().enumerate() {
221            let circuit_str =
222                AzureQuantumClient::circuit_to_provider_format(circuit, &self.provider_id)?;
223            let job_name = format!(
224                "quantrs_batch_{}_job_{}",
225                chrono::Utc::now().timestamp(),
226                idx
227            );
228
229            let config = AzureCircuitConfig {
230                name: job_name,
231                circuit: circuit_str,
232                shots: shots.max(1), // Ensure at least 1 shot
233                provider_parameters: self.config.provider_parameters.clone(),
234            };
235
236            configs.push(config);
237        }
238
239        // Submit all circuits in parallel
240        let job_ids = self
241            .client
242            .submit_circuits_parallel(&self.target_id, &self.provider_id, configs)
243            .await?;
244
245        // Wait for all jobs to complete and collect results
246        let mut results = Vec::with_capacity(job_ids.len());
247        for job_id in job_ids {
248            let result = self
249                .client
250                .wait_for_job(&job_id, self.config.timeout_secs)
251                .await?;
252
253            let mut counts = HashMap::new();
254            for (bitstring, probability) in result.histogram {
255                // Convert probabilities to counts based on shots
256                let count = (probability * shots as f64).round() as usize;
257                counts.insert(bitstring, count);
258            }
259
260            let mut metadata = HashMap::new();
261            metadata.insert("job_id".to_string(), job_id);
262            metadata.insert("provider".to_string(), self.provider_id.clone());
263            metadata.insert("target".to_string(), self.target_id.clone());
264
265            results.push(CircuitResult {
266                counts,
267                shots,
268                metadata,
269            });
270        }
271
272        Ok(results)
273    }
274
275    async fn can_execute_circuit<const N: usize>(
276        &self,
277        circuit: &Circuit<N>,
278    ) -> DeviceResult<bool> {
279        // Get device qubit count
280        let device_qubits = self.qubit_count().await?;
281
282        // Check if circuit qubit count exceeds device qubit count
283        if N > device_qubits {
284            return Ok(false);
285        }
286
287        // Check if the circuit can be converted to the provider's format
288        // This is just a basic check, more sophisticated validation would be done
289        // when actually converting the circuit
290        match AzureQuantumClient::circuit_to_provider_format(circuit, &self.provider_id) {
291            Ok(_) => Ok(true),
292            Err(_) => Ok(false),
293        }
294    }
295
296    async fn estimated_queue_time<const N: usize>(
297        &self,
298        _circuit: &Circuit<N>,
299    ) -> DeviceResult<Duration> {
300        // Azure Quantum doesn't provide queue time estimates in the API
301        // Return a conservative estimate based on device type
302        let is_sim = self.is_simulator().await?;
303
304        if is_sim {
305            // Simulators tend to have shorter queue times
306            Ok(Duration::from_secs(60)) // 1 minute
307        } else {
308            // Hardware devices tend to have longer queue times
309            Ok(Duration::from_secs(300)) // 5 minutes
310        }
311    }
312}
313
314#[cfg(not(feature = "azure"))]
315pub struct AzureQuantumDevice;
316
317#[cfg(not(feature = "azure"))]
318impl AzureQuantumDevice {
319    pub async fn new(
320        _client: crate::azure::AzureQuantumClient,
321        _target_id: &str,
322        _provider_id: Option<&str>,
323        _config: Option<AzureDeviceConfig>,
324    ) -> DeviceResult<Self> {
325        Err(DeviceError::UnsupportedDevice(
326            "Azure Quantum support not enabled. Recompile with the 'azure' feature.".to_string(),
327        ))
328    }
329}