1#[cfg(feature = "ibm")]
2use async_trait::async_trait;
3#[cfg(feature = "ibm")]
4use chrono;
5use quantrs2_circuit::prelude::Circuit;
6use std::collections::HashMap;
7#[cfg(feature = "ibm")]
8use std::sync::Arc;
9#[cfg(feature = "ibm")]
10use std::time::Duration;
11
12#[cfg(feature = "ibm")]
13use crate::{
14 ibm::IBMQuantumClient, CircuitExecutor, CircuitResult, DeviceError, DeviceResult, QuantumDevice,
15};
16
17#[cfg(not(feature = "ibm"))]
18use crate::{
19 ibm::IBMQuantumClient, CircuitExecutor, CircuitResult, DeviceError, DeviceResult, QuantumDevice,
20};
21
22#[cfg(feature = "ibm")]
24pub struct IBMQuantumDevice {
25 client: Arc<IBMQuantumClient>,
27 backend: crate::ibm::IBMBackend,
29 config: IBMDeviceConfig,
31}
32
33#[cfg(not(feature = "ibm"))]
34pub struct IBMQuantumDevice;
35
36#[derive(Debug, Clone)]
38pub struct IBMDeviceConfig {
39 pub default_shots: usize,
41 pub optimization_level: usize,
43 pub timeout_seconds: u64,
45 pub optimize_routing: bool,
47 pub max_parallel_jobs: usize,
49}
50
51#[cfg(feature = "ibm")]
52impl Default for IBMDeviceConfig {
53 fn default() -> Self {
54 Self {
55 default_shots: 1024,
56 optimization_level: 1,
57 timeout_seconds: 300,
58 optimize_routing: true,
59 max_parallel_jobs: 5,
60 }
61 }
62}
63
64#[cfg(not(feature = "ibm"))]
65impl Default for IBMDeviceConfig {
66 fn default() -> Self {
67 Self {
68 default_shots: 1024,
69 optimization_level: 1,
70 timeout_seconds: 300,
71 optimize_routing: true,
72 max_parallel_jobs: 5,
73 }
74 }
75}
76
77#[cfg(feature = "ibm")]
78impl IBMQuantumDevice {
79 pub async fn new(
81 client: IBMQuantumClient,
82 backend_name: &str,
83 config: Option<IBMDeviceConfig>,
84 ) -> DeviceResult<Self> {
85 let backend = client.get_backend(backend_name).await?;
86 let client = Arc::new(client);
87
88 Ok(Self {
89 client,
90 backend,
91 config: config.unwrap_or_default(),
92 })
93 }
94
95 fn create_circuit_config<const N: usize>(
97 &self,
98 circuit: &Circuit<N>,
99 shots: Option<usize>,
100 ) -> DeviceResult<crate::ibm::IBMCircuitConfig> {
101 let qasm = self.circuit_to_qasm(circuit)?;
102 let shots = shots.unwrap_or(self.config.default_shots);
103
104 Ok(crate::ibm::IBMCircuitConfig {
105 name: format!("quantrs_circuit_{}", chrono::Utc::now().timestamp()),
106 qasm,
107 shots,
108 optimization_level: Some(self.config.optimization_level),
109 initial_layout: None, })
111 }
112
113 fn circuit_to_qasm<const N: usize>(&self, _circuit: &Circuit<N>) -> DeviceResult<String> {
115 if N > self.backend.n_qubits {
116 return Err(DeviceError::CircuitConversion(format!(
117 "Circuit has {} qubits but backend {} only supports {} qubits",
118 N, self.backend.name, self.backend.n_qubits
119 )));
120 }
121
122 let mut qasm = String::from("OPENQASM 2.0;\ninclude \"qelib1.inc\";\n\n");
124
125 qasm.push_str(&format!("qreg q[{}];\n", N));
127 qasm.push_str(&format!("creg c[{}];\n\n", N));
128
129 Ok(qasm)
134 }
135}
136
137#[cfg(not(feature = "ibm"))]
138impl IBMQuantumDevice {
139 pub async fn new(
141 _client: IBMQuantumClient,
142 _backend_name: &str,
143 _config: Option<IBMDeviceConfig>,
144 ) -> DeviceResult<Self> {
145 Err(DeviceError::UnsupportedDevice(
146 "IBM Quantum support not enabled. Recompile with the 'ibm' feature.".to_string(),
147 ))
148 }
149}
150
151#[cfg(feature = "ibm")]
152#[async_trait]
153impl QuantumDevice for IBMQuantumDevice {
154 async fn is_available(&self) -> DeviceResult<bool> {
155 let backend = self.client.get_backend(&self.backend.name).await?;
157 Ok(backend.status == "active")
158 }
159
160 async fn qubit_count(&self) -> DeviceResult<usize> {
161 Ok(self.backend.n_qubits)
162 }
163
164 async fn properties(&self) -> DeviceResult<HashMap<String, String>> {
165 let mut props = HashMap::new();
168 props.insert("name".to_string(), self.backend.name.clone());
169 props.insert("description".to_string(), self.backend.description.clone());
170 props.insert("version".to_string(), self.backend.version.clone());
171 props.insert("n_qubits".to_string(), self.backend.n_qubits.to_string());
172 props.insert("simulator".to_string(), self.backend.simulator.to_string());
173
174 Ok(props)
175 }
176
177 async fn is_simulator(&self) -> DeviceResult<bool> {
178 Ok(self.backend.simulator)
179 }
180}
181
182#[cfg(not(feature = "ibm"))]
183impl QuantumDevice for IBMQuantumDevice {
184 fn is_available(&self) -> DeviceResult<bool> {
185 Err(DeviceError::UnsupportedDevice(
186 "IBM Quantum support not enabled".to_string(),
187 ))
188 }
189
190 fn qubit_count(&self) -> DeviceResult<usize> {
191 Err(DeviceError::UnsupportedDevice(
192 "IBM Quantum support not enabled".to_string(),
193 ))
194 }
195
196 fn properties(&self) -> DeviceResult<HashMap<String, String>> {
197 Err(DeviceError::UnsupportedDevice(
198 "IBM Quantum support not enabled".to_string(),
199 ))
200 }
201
202 fn is_simulator(&self) -> DeviceResult<bool> {
203 Err(DeviceError::UnsupportedDevice(
204 "IBM Quantum support not enabled".to_string(),
205 ))
206 }
207}
208
209#[cfg(feature = "ibm")]
210#[async_trait]
211impl CircuitExecutor for IBMQuantumDevice {
212 async fn execute_circuit<const N: usize>(
213 &self,
214 circuit: &Circuit<N>,
215 shots: usize,
216 ) -> DeviceResult<CircuitResult> {
217 let config = self.create_circuit_config(circuit, Some(shots))?;
219
220 let job_id = self
222 .client
223 .submit_circuit(&self.backend.name, config)
224 .await?;
225
226 let result = self
228 .client
229 .wait_for_job(&job_id, Some(self.config.timeout_seconds))
230 .await?;
231
232 let mut metadata = HashMap::new();
234 metadata.insert("job_id".to_string(), job_id);
235 metadata.insert("backend".to_string(), self.backend.name.clone());
236 metadata.insert("shots".to_string(), shots.to_string());
237
238 Ok(CircuitResult {
239 counts: result.counts,
240 shots: result.shots,
241 metadata,
242 })
243 }
244
245 async fn execute_circuits<const N: usize>(
246 &self,
247 circuits: Vec<&Circuit<N>>,
248 shots: usize,
249 ) -> DeviceResult<Vec<CircuitResult>> {
250 if circuits.is_empty() {
251 return Ok(Vec::new());
252 }
253
254 let chunk_size = self.config.max_parallel_jobs.max(1);
256 let mut results = Vec::new();
257
258 for chunk in circuits.chunks(chunk_size) {
260 let mut configs = Vec::new();
261
262 for circuit in chunk {
264 let config = self.create_circuit_config(circuit, Some(shots))?;
265 configs.push(config);
266 }
267
268 let job_ids = self
270 .client
271 .submit_circuits_parallel(&self.backend.name, configs)
272 .await?;
273
274 let mut chunk_results = Vec::new();
276 for job_id in job_ids {
277 let result = self
278 .client
279 .wait_for_job(&job_id, Some(self.config.timeout_seconds))
280 .await?;
281
282 let mut metadata = HashMap::new();
283 metadata.insert("job_id".to_string(), job_id);
284 metadata.insert("backend".to_string(), self.backend.name.clone());
285 metadata.insert("shots".to_string(), shots.to_string());
286
287 chunk_results.push(CircuitResult {
288 counts: result.counts,
289 shots: result.shots,
290 metadata,
291 });
292 }
293
294 results.extend(chunk_results);
295 }
296
297 Ok(results)
298 }
299
300 async fn can_execute_circuit<const N: usize>(
301 &self,
302 _circuit: &Circuit<N>,
303 ) -> DeviceResult<bool> {
304 if N > self.backend.n_qubits {
306 return Ok(false);
307 }
308
309 Ok(true)
316 }
317
318 async fn estimated_queue_time<const N: usize>(
319 &self,
320 _circuit: &Circuit<N>,
321 ) -> DeviceResult<Duration> {
322 if self.backend.simulator {
327 Ok(Duration::from_secs(10)) } else {
329 Ok(Duration::from_secs(3600)) }
331 }
332}
333
334#[cfg(not(feature = "ibm"))]
335impl CircuitExecutor for IBMQuantumDevice {
336 fn execute_circuit<const N: usize>(
337 &self,
338 _circuit: &Circuit<N>,
339 _shots: usize,
340 ) -> DeviceResult<CircuitResult> {
341 Err(DeviceError::UnsupportedDevice(
342 "IBM Quantum support not enabled".to_string(),
343 ))
344 }
345
346 fn execute_circuits<const N: usize>(
347 &self,
348 _circuits: Vec<&Circuit<N>>,
349 _shots: usize,
350 ) -> DeviceResult<Vec<CircuitResult>> {
351 Err(DeviceError::UnsupportedDevice(
352 "IBM Quantum support not enabled".to_string(),
353 ))
354 }
355
356 fn can_execute_circuit<const N: usize>(&self, _circuit: &Circuit<N>) -> DeviceResult<bool> {
357 Err(DeviceError::UnsupportedDevice(
358 "IBM Quantum support not enabled".to_string(),
359 ))
360 }
361
362 fn estimated_queue_time<const N: usize>(
363 &self,
364 _circuit: &Circuit<N>,
365 ) -> DeviceResult<std::time::Duration> {
366 Err(DeviceError::UnsupportedDevice(
367 "IBM Quantum support not enabled".to_string(),
368 ))
369 }
370}