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#[derive(Debug, Clone)]
25pub struct AzureDeviceConfig {
26 pub provider_id: String,
28 pub default_shots: usize,
30 #[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 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#[cfg(feature = "azure")]
55pub struct AzureQuantumDevice {
56 client: Arc<AzureQuantumClient>,
58 target_id: String,
60 provider_id: String,
62 config: AzureDeviceConfig,
64 target_cache: Arc<RwLock<Option<AzureTarget>>>,
66}
67
68#[cfg(feature = "azure")]
69impl AzureQuantumDevice {
70 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 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 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 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 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 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 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 let circuit_str =
169 AzureQuantumClient::circuit_to_provider_format(circuit, &self.provider_id)?;
170
171 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), provider_parameters: self.config.provider_parameters.clone(),
178 };
179
180 let job_id = self
182 .client
183 .submit_circuit(&self.target_id, &self.provider_id, config)
184 .await?;
185
186 let result = self
188 .client
189 .wait_for_job(&job_id, self.config.timeout_secs)
190 .await?;
191
192 let mut counts = HashMap::new();
194 for (bitstring, probability) in result.histogram {
195 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 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), provider_parameters: self.config.provider_parameters.clone(),
234 };
235
236 configs.push(config);
237 }
238
239 let job_ids = self
241 .client
242 .submit_circuits_parallel(&self.target_id, &self.provider_id, configs)
243 .await?;
244
245 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 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 let device_qubits = self.qubit_count().await?;
281
282 if N > device_qubits {
284 return Ok(false);
285 }
286
287 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 let is_sim = self.is_simulator().await?;
303
304 if is_sim {
305 Ok(Duration::from_secs(60)) } else {
308 Ok(Duration::from_secs(300)) }
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}