1use quantrs2_circuit::prelude::Circuit;
8#[cfg(feature = "ibm")]
9use std::collections::HashMap;
10#[cfg(feature = "ibm")]
11use std::sync::Arc;
12#[cfg(feature = "ibm")]
13use std::thread::sleep;
14#[cfg(feature = "ibm")]
15use std::time::{Duration, Instant, SystemTime};
16#[cfg(feature = "ibm")]
17use tokio::sync::RwLock;
18
19#[cfg(feature = "ibm")]
20use reqwest::{header, Client};
21#[cfg(feature = "ibm")]
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24
25use crate::DeviceError;
26use crate::DeviceResult;
27
28#[cfg(feature = "ibm")]
29const IBM_QUANTUM_API_URL: &str = "https://api.quantum-computing.ibm.com/api";
30#[cfg(feature = "ibm")]
31const IBM_AUTH_URL: &str = "https://auth.quantum-computing.ibm.com/api";
32#[cfg(feature = "ibm")]
33const DEFAULT_TIMEOUT_SECS: u64 = 90;
34#[cfg(feature = "ibm")]
36const TOKEN_REFRESH_BUFFER_SECS: u64 = 300;
37#[cfg(feature = "ibm")]
39const DEFAULT_TOKEN_VALIDITY_SECS: u64 = 3600;
40#[cfg(feature = "ibm")]
42const DEFAULT_MAX_RETRIES: u32 = 3;
43#[cfg(feature = "ibm")]
45const DEFAULT_INITIAL_RETRY_DELAY_MS: u64 = 100;
46#[cfg(feature = "ibm")]
48const DEFAULT_MAX_RETRY_DELAY_MS: u64 = 30000;
49#[cfg(feature = "ibm")]
51const DEFAULT_BACKOFF_MULTIPLIER: f64 = 2.0;
52
53#[cfg(feature = "ibm")]
55#[derive(Debug, Clone)]
56pub struct IBMRetryConfig {
57 pub max_attempts: u32,
59 pub initial_delay: Duration,
61 pub max_delay: Duration,
63 pub backoff_multiplier: f64,
65 pub jitter_factor: f64,
67}
68
69#[cfg(feature = "ibm")]
70impl Default for IBMRetryConfig {
71 fn default() -> Self {
72 Self {
73 max_attempts: DEFAULT_MAX_RETRIES,
74 initial_delay: Duration::from_millis(DEFAULT_INITIAL_RETRY_DELAY_MS),
75 max_delay: Duration::from_millis(DEFAULT_MAX_RETRY_DELAY_MS),
76 backoff_multiplier: DEFAULT_BACKOFF_MULTIPLIER,
77 jitter_factor: 0.1,
78 }
79 }
80}
81
82#[cfg(feature = "ibm")]
83impl IBMRetryConfig {
84 pub const fn aggressive() -> Self {
86 Self {
87 max_attempts: 5,
88 initial_delay: Duration::from_millis(50),
89 max_delay: Duration::from_secs(10),
90 backoff_multiplier: 2.0,
91 jitter_factor: 0.2,
92 }
93 }
94
95 pub const fn patient() -> Self {
97 Self {
98 max_attempts: 3,
99 initial_delay: Duration::from_secs(1),
100 max_delay: Duration::from_secs(60),
101 backoff_multiplier: 3.0,
102 jitter_factor: 0.3,
103 }
104 }
105}
106
107#[cfg(feature = "ibm")]
109#[derive(Debug, Clone)]
110pub struct TokenInfo {
111 pub access_token: String,
113 pub obtained_at: Instant,
115 pub valid_for_secs: u64,
117}
118
119#[cfg(feature = "ibm")]
120impl TokenInfo {
121 pub fn is_expired(&self) -> bool {
123 let elapsed = self.obtained_at.elapsed().as_secs();
124 elapsed + TOKEN_REFRESH_BUFFER_SECS >= self.valid_for_secs
125 }
126
127 pub fn remaining_secs(&self) -> u64 {
129 let elapsed = self.obtained_at.elapsed().as_secs();
130 self.valid_for_secs.saturating_sub(elapsed)
131 }
132}
133
134#[cfg(feature = "ibm")]
136#[derive(Debug, Deserialize)]
137struct AuthResponse {
138 id: String,
140 ttl: Option<u64>,
142}
143
144#[cfg(feature = "ibm")]
146#[derive(Debug, Clone)]
147pub struct IBMAuthConfig {
148 pub api_key: String,
150 pub auto_refresh: bool,
152 pub token_validity_secs: Option<u64>,
154}
155
156#[derive(Debug, Clone)]
158#[cfg_attr(feature = "ibm", derive(serde::Deserialize))]
159pub struct IBMBackend {
160 pub id: String,
162 pub name: String,
164 pub simulator: bool,
166 pub n_qubits: usize,
168 pub status: String,
170 pub description: String,
172 pub version: String,
174}
175
176#[derive(Debug, Clone)]
178#[cfg_attr(feature = "ibm", derive(Serialize))]
179pub struct IBMCircuitConfig {
180 pub name: String,
182 pub qasm: String,
184 pub shots: usize,
186 pub optimization_level: Option<usize>,
188 pub initial_layout: Option<std::collections::HashMap<String, usize>>,
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194#[cfg_attr(feature = "ibm", derive(Deserialize))]
195pub enum IBMJobStatus {
196 #[cfg_attr(feature = "ibm", serde(rename = "CREATING"))]
197 Creating,
198 #[cfg_attr(feature = "ibm", serde(rename = "CREATED"))]
199 Created,
200 #[cfg_attr(feature = "ibm", serde(rename = "VALIDATING"))]
201 Validating,
202 #[cfg_attr(feature = "ibm", serde(rename = "VALIDATED"))]
203 Validated,
204 #[cfg_attr(feature = "ibm", serde(rename = "QUEUED"))]
205 Queued,
206 #[cfg_attr(feature = "ibm", serde(rename = "RUNNING"))]
207 Running,
208 #[cfg_attr(feature = "ibm", serde(rename = "COMPLETED"))]
209 Completed,
210 #[cfg_attr(feature = "ibm", serde(rename = "CANCELLED"))]
211 Cancelled,
212 #[cfg_attr(feature = "ibm", serde(rename = "ERROR"))]
213 Error,
214}
215
216#[cfg(feature = "ibm")]
218#[derive(Debug, Deserialize)]
219pub struct IBMJobResponse {
220 pub id: String,
222 pub status: IBMJobStatus,
224 pub shots: usize,
226 pub backend: IBMBackend,
228}
229
230#[cfg(not(feature = "ibm"))]
231#[derive(Debug)]
232pub struct IBMJobResponse {
233 pub id: String,
235 pub status: IBMJobStatus,
237 pub shots: usize,
239}
240
241#[cfg(feature = "ibm")]
243#[derive(Debug, Deserialize)]
244pub struct IBMJobResult {
245 pub counts: HashMap<String, usize>,
247 pub shots: usize,
249 pub status: IBMJobStatus,
251 pub error: Option<String>,
253}
254
255#[cfg(not(feature = "ibm"))]
256#[derive(Debug)]
257pub struct IBMJobResult {
258 pub counts: std::collections::HashMap<String, usize>,
260 pub shots: usize,
262 pub status: IBMJobStatus,
264 pub error: Option<String>,
266}
267
268#[derive(Error, Debug)]
270#[non_exhaustive]
271pub enum IBMQuantumError {
272 #[error("Authentication error: {0}")]
273 Authentication(String),
274
275 #[error("API error: {0}")]
276 API(String),
277
278 #[error("Backend not available: {0}")]
279 BackendUnavailable(String),
280
281 #[error("QASM conversion error: {0}")]
282 QasmConversion(String),
283
284 #[error("Job submission error: {0}")]
285 JobSubmission(String),
286
287 #[error("Timeout waiting for job completion")]
288 Timeout,
289}
290
291#[cfg(feature = "ibm")]
293pub struct IBMQuantumClient {
294 client: Client,
296 api_url: String,
298 auth_url: String,
300 token_info: Arc<RwLock<TokenInfo>>,
302 auth_config: IBMAuthConfig,
304 retry_config: IBMRetryConfig,
306}
307
308#[cfg(feature = "ibm")]
309impl Clone for IBMQuantumClient {
310 fn clone(&self) -> Self {
311 Self {
312 client: self.client.clone(),
313 api_url: self.api_url.clone(),
314 auth_url: self.auth_url.clone(),
315 token_info: Arc::clone(&self.token_info),
316 auth_config: self.auth_config.clone(),
317 retry_config: self.retry_config.clone(),
318 }
319 }
320}
321
322#[cfg(not(feature = "ibm"))]
323#[derive(Clone)]
324pub struct IBMQuantumClient;
325
326#[cfg(feature = "ibm")]
327impl IBMQuantumClient {
328 pub fn new(token: &str) -> DeviceResult<Self> {
333 let mut headers = header::HeaderMap::new();
334 headers.insert(
335 header::CONTENT_TYPE,
336 header::HeaderValue::from_static("application/json"),
337 );
338
339 let client = Client::builder()
340 .default_headers(headers)
341 .timeout(Duration::from_secs(30))
342 .build()
343 .map_err(|e| DeviceError::Connection(e.to_string()))?;
344
345 let token_info = TokenInfo {
346 access_token: token.to_string(),
347 obtained_at: Instant::now(),
348 valid_for_secs: DEFAULT_TOKEN_VALIDITY_SECS,
349 };
350
351 Ok(Self {
352 client,
353 api_url: IBM_QUANTUM_API_URL.to_string(),
354 auth_url: IBM_AUTH_URL.to_string(),
355 token_info: Arc::new(RwLock::new(token_info)),
356 auth_config: IBMAuthConfig {
357 api_key: String::new(), auto_refresh: false,
359 token_validity_secs: None,
360 },
361 retry_config: IBMRetryConfig::default(),
362 })
363 }
364
365 pub async fn new_with_api_key(api_key: &str) -> DeviceResult<Self> {
370 Self::new_with_config(IBMAuthConfig {
371 api_key: api_key.to_string(),
372 auto_refresh: true,
373 token_validity_secs: None,
374 })
375 .await
376 }
377
378 pub async fn new_with_config(config: IBMAuthConfig) -> DeviceResult<Self> {
380 Self::new_with_config_and_retry(config, IBMRetryConfig::default()).await
381 }
382
383 pub async fn new_with_config_and_retry(
385 config: IBMAuthConfig,
386 retry_config: IBMRetryConfig,
387 ) -> DeviceResult<Self> {
388 let mut headers = header::HeaderMap::new();
389 headers.insert(
390 header::CONTENT_TYPE,
391 header::HeaderValue::from_static("application/json"),
392 );
393
394 let client = Client::builder()
395 .default_headers(headers)
396 .timeout(Duration::from_secs(30))
397 .build()
398 .map_err(|e| DeviceError::Connection(e.to_string()))?;
399
400 let token_info = Self::exchange_api_key_for_token(&client, &config.api_key).await?;
402
403 Ok(Self {
404 client,
405 api_url: IBM_QUANTUM_API_URL.to_string(),
406 auth_url: IBM_AUTH_URL.to_string(),
407 token_info: Arc::new(RwLock::new(token_info)),
408 auth_config: config,
409 retry_config,
410 })
411 }
412
413 pub const fn set_retry_config(&mut self, config: IBMRetryConfig) {
415 self.retry_config = config;
416 }
417
418 pub const fn retry_config(&self) -> &IBMRetryConfig {
420 &self.retry_config
421 }
422
423 async fn with_retry<F, Fut, T>(&self, operation: F) -> DeviceResult<T>
425 where
426 F: Fn() -> Fut,
427 Fut: std::future::Future<Output = DeviceResult<T>>,
428 {
429 use scirs2_core::random::prelude::*;
430
431 let mut attempt = 0;
432 let mut delay = self.retry_config.initial_delay;
433
434 loop {
435 match operation().await {
436 Ok(result) => return Ok(result),
437 Err(err) => {
438 attempt += 1;
439
440 let is_retryable = match &err {
442 DeviceError::Connection(_) | DeviceError::Timeout(_) => true,
443 DeviceError::APIError(msg) => {
444 msg.contains("rate") || msg.contains('5') || msg.contains("503")
445 }
446 _ => false,
447 };
448
449 if !is_retryable || attempt >= self.retry_config.max_attempts {
450 return Err(err);
451 }
452
453 let jitter = if self.retry_config.jitter_factor > 0.0 {
455 let mut rng = thread_rng();
456 let jitter_range =
457 delay.as_millis() as f64 * self.retry_config.jitter_factor;
458 Duration::from_millis((rng.random::<f64>() * jitter_range) as u64)
459 } else {
460 Duration::ZERO
461 };
462
463 let actual_delay = delay + jitter;
464 tokio::time::sleep(actual_delay).await;
465
466 delay = Duration::from_millis(
468 (delay.as_millis() as f64 * self.retry_config.backoff_multiplier) as u64,
469 )
470 .min(self.retry_config.max_delay);
471 }
472 }
473 }
474 }
475
476 async fn exchange_api_key_for_token(client: &Client, api_key: &str) -> DeviceResult<TokenInfo> {
478 let response = client
479 .post(format!("{IBM_AUTH_URL}/users/loginWithToken"))
480 .json(&serde_json::json!({ "apiToken": api_key }))
481 .send()
482 .await
483 .map_err(|e| DeviceError::Connection(format!("Authentication request failed: {e}")))?;
484
485 if !response.status().is_success() {
486 let error_msg = response
487 .text()
488 .await
489 .unwrap_or_else(|_| "Unknown authentication error".to_string());
490 return Err(DeviceError::Authentication(error_msg));
491 }
492
493 let auth_response: AuthResponse = response.json().await.map_err(|e| {
494 DeviceError::Deserialization(format!("Failed to parse auth response: {e}"))
495 })?;
496
497 let valid_for_secs = auth_response.ttl.unwrap_or(DEFAULT_TOKEN_VALIDITY_SECS);
498
499 Ok(TokenInfo {
500 access_token: auth_response.id,
501 obtained_at: Instant::now(),
502 valid_for_secs,
503 })
504 }
505
506 pub async fn refresh_token(&self) -> DeviceResult<()> {
508 if self.auth_config.api_key.is_empty() {
509 return Err(DeviceError::Authentication(
510 "Cannot refresh token: no API key configured. Use new_with_api_key() for auto-refresh support.".to_string()
511 ));
512 }
513
514 let new_token_info =
515 Self::exchange_api_key_for_token(&self.client, &self.auth_config.api_key).await?;
516
517 let mut token_guard = self.token_info.write().await;
518 *token_guard = new_token_info;
519
520 Ok(())
521 }
522
523 async fn get_valid_token(&self) -> DeviceResult<String> {
525 let needs_refresh = {
527 let token_guard = self.token_info.read().await;
528 token_guard.is_expired()
529 };
530
531 if needs_refresh && self.auth_config.auto_refresh {
532 self.refresh_token().await?;
533 }
534
535 let token_guard = self.token_info.read().await;
536
537 if token_guard.is_expired() && !self.auth_config.auto_refresh {
539 }
542
543 Ok(token_guard.access_token.clone())
544 }
545
546 pub async fn is_token_valid(&self) -> bool {
548 let token_guard = self.token_info.read().await;
549 !token_guard.is_expired()
550 }
551
552 pub async fn token_info(&self) -> TokenInfo {
554 let token_guard = self.token_info.read().await;
555 token_guard.clone()
556 }
557
558 pub async fn list_backends_with_retry(&self) -> DeviceResult<Vec<IBMBackend>> {
560 self.with_retry(|| async { self.list_backends().await })
561 .await
562 }
563
564 pub async fn list_backends(&self) -> DeviceResult<Vec<IBMBackend>> {
566 let token = self.get_valid_token().await?;
567
568 let response = self
569 .client
570 .get(format!("{}/backends", self.api_url))
571 .header("Authorization", format!("Bearer {token}"))
572 .send()
573 .await
574 .map_err(|e| DeviceError::Connection(e.to_string()))?;
575
576 if !response.status().is_success() {
577 let error_msg = response
578 .text()
579 .await
580 .unwrap_or_else(|_| "Unknown error".to_string());
581 return Err(DeviceError::APIError(error_msg));
582 }
583
584 let backends: Vec<IBMBackend> = response
585 .json()
586 .await
587 .map_err(|e| DeviceError::Deserialization(e.to_string()))?;
588
589 Ok(backends)
590 }
591
592 pub async fn get_backend(&self, backend_name: &str) -> DeviceResult<IBMBackend> {
594 let token = self.get_valid_token().await?;
595
596 let response = self
597 .client
598 .get(format!("{}/backends/{}", self.api_url, backend_name))
599 .header("Authorization", format!("Bearer {token}"))
600 .send()
601 .await
602 .map_err(|e| DeviceError::Connection(e.to_string()))?;
603
604 if !response.status().is_success() {
605 let error_msg = response
606 .text()
607 .await
608 .unwrap_or_else(|_| "Unknown error".to_string());
609 return Err(DeviceError::APIError(error_msg));
610 }
611
612 let backend: IBMBackend = response
613 .json()
614 .await
615 .map_err(|e| DeviceError::Deserialization(e.to_string()))?;
616
617 Ok(backend)
618 }
619
620 pub async fn submit_circuit(
622 &self,
623 backend_name: &str,
624 config: IBMCircuitConfig,
625 ) -> DeviceResult<String> {
626 #[cfg(feature = "ibm")]
627 {
628 use serde_json::json;
629
630 let token = self.get_valid_token().await?;
631
632 let payload = json!({
633 "backend": backend_name,
634 "name": config.name,
635 "qasm": config.qasm,
636 "shots": config.shots,
637 "optimization_level": config.optimization_level.unwrap_or(1),
638 "initial_layout": config.initial_layout.unwrap_or_default(),
639 });
640
641 let response = self
642 .client
643 .post(format!("{}/jobs", self.api_url))
644 .header("Authorization", format!("Bearer {token}"))
645 .json(&payload)
646 .send()
647 .await
648 .map_err(|e| DeviceError::Connection(e.to_string()))?;
649
650 if !response.status().is_success() {
651 let error_msg = response
652 .text()
653 .await
654 .unwrap_or_else(|_| "Unknown error".to_string());
655 return Err(DeviceError::JobSubmission(error_msg));
656 }
657
658 let job_response: IBMJobResponse = response
659 .json()
660 .await
661 .map_err(|e| DeviceError::Deserialization(e.to_string()))?;
662
663 Ok(job_response.id)
664 }
665
666 #[cfg(not(feature = "ibm"))]
667 Err(DeviceError::UnsupportedDevice(
668 "IBM Quantum support not enabled".to_string(),
669 ))
670 }
671
672 pub async fn get_job_status(&self, job_id: &str) -> DeviceResult<IBMJobStatus> {
674 let token = self.get_valid_token().await?;
675
676 let response = self
677 .client
678 .get(format!("{}/jobs/{}", self.api_url, job_id))
679 .header("Authorization", format!("Bearer {token}"))
680 .send()
681 .await
682 .map_err(|e| DeviceError::Connection(e.to_string()))?;
683
684 if !response.status().is_success() {
685 let error_msg = response
686 .text()
687 .await
688 .unwrap_or_else(|_| "Unknown error".to_string());
689 return Err(DeviceError::APIError(error_msg));
690 }
691
692 let job: IBMJobResponse = response
693 .json()
694 .await
695 .map_err(|e| DeviceError::Deserialization(e.to_string()))?;
696
697 Ok(job.status)
698 }
699
700 pub async fn get_job_result(&self, job_id: &str) -> DeviceResult<IBMJobResult> {
702 let token = self.get_valid_token().await?;
703
704 let response = self
705 .client
706 .get(format!("{}/jobs/{}/result", self.api_url, job_id))
707 .header("Authorization", format!("Bearer {token}"))
708 .send()
709 .await
710 .map_err(|e| DeviceError::Connection(e.to_string()))?;
711
712 if !response.status().is_success() {
713 let error_msg = response
714 .text()
715 .await
716 .unwrap_or_else(|_| "Unknown error".to_string());
717 return Err(DeviceError::APIError(error_msg));
718 }
719
720 let result: IBMJobResult = response
721 .json()
722 .await
723 .map_err(|e| DeviceError::Deserialization(e.to_string()))?;
724
725 Ok(result)
726 }
727
728 pub async fn wait_for_job(
730 &self,
731 job_id: &str,
732 timeout_secs: Option<u64>,
733 ) -> DeviceResult<IBMJobResult> {
734 let timeout = timeout_secs.unwrap_or(DEFAULT_TIMEOUT_SECS);
735 let mut elapsed = 0;
736 let interval = 5; while elapsed < timeout {
739 let status = self.get_job_status(job_id).await?;
740
741 match status {
742 IBMJobStatus::Completed => {
743 return self.get_job_result(job_id).await;
744 }
745 IBMJobStatus::Error => {
746 return Err(DeviceError::JobExecution(format!(
747 "Job {job_id} encountered an error"
748 )));
749 }
750 IBMJobStatus::Cancelled => {
751 return Err(DeviceError::JobExecution(format!(
752 "Job {job_id} was cancelled"
753 )));
754 }
755 _ => {
756 sleep(Duration::from_secs(interval));
758 elapsed += interval;
759 }
760 }
761 }
762
763 Err(DeviceError::Timeout(format!(
764 "Timed out waiting for job {job_id} to complete"
765 )))
766 }
767
768 pub async fn submit_circuits_parallel(
770 &self,
771 backend_name: &str,
772 configs: Vec<IBMCircuitConfig>,
773 ) -> DeviceResult<Vec<String>> {
774 #[cfg(feature = "ibm")]
775 {
776 use tokio::task;
777
778 let client = Arc::new(self.clone());
779
780 let mut handles = vec![];
781
782 for config in configs {
783 let client_clone = client.clone();
784 let backend_name = backend_name.to_string();
785
786 let handle =
787 task::spawn(
788 async move { client_clone.submit_circuit(&backend_name, config).await },
789 );
790
791 handles.push(handle);
792 }
793
794 let mut job_ids = vec![];
795
796 for handle in handles {
797 match handle.await {
798 Ok(result) => match result {
799 Ok(job_id) => job_ids.push(job_id),
800 Err(e) => return Err(e),
801 },
802 Err(e) => {
803 return Err(DeviceError::JobSubmission(format!(
804 "Failed to join task: {e}"
805 )));
806 }
807 }
808 }
809
810 Ok(job_ids)
811 }
812
813 #[cfg(not(feature = "ibm"))]
814 Err(DeviceError::UnsupportedDevice(
815 "IBM Quantum support not enabled".to_string(),
816 ))
817 }
818
819 pub fn circuit_to_qasm<const N: usize>(
821 _circuit: &Circuit<N>,
822 _initial_layout: Option<std::collections::HashMap<String, usize>>,
823 ) -> DeviceResult<String> {
824 let mut qasm = String::from("OPENQASM 2.0;\ninclude \"qelib1.inc\";\n\n");
829
830 use std::fmt::Write;
832 writeln!(qasm, "qreg q[{N}];")
833 .map_err(|e| DeviceError::CircuitConversion(format!("Failed to write QASM: {e}")))?;
834 writeln!(qasm, "creg c[{N}];")
835 .map_err(|e| DeviceError::CircuitConversion(format!("Failed to write QASM: {e}")))?;
836
837 Ok(qasm)
845 }
846}
847
848#[cfg(not(feature = "ibm"))]
849impl IBMQuantumClient {
850 pub fn new(_token: &str) -> DeviceResult<Self> {
851 Err(DeviceError::UnsupportedDevice(
852 "IBM Quantum support not enabled. Recompile with the 'ibm' feature.".to_string(),
853 ))
854 }
855
856 pub async fn list_backends(&self) -> DeviceResult<Vec<IBMBackend>> {
857 Err(DeviceError::UnsupportedDevice(
858 "IBM Quantum support not enabled".to_string(),
859 ))
860 }
861
862 pub async fn get_backend(&self, _backend_name: &str) -> DeviceResult<IBMBackend> {
863 Err(DeviceError::UnsupportedDevice(
864 "IBM Quantum support not enabled".to_string(),
865 ))
866 }
867
868 pub async fn submit_circuit(
869 &self,
870 _backend_name: &str,
871 _config: IBMCircuitConfig,
872 ) -> DeviceResult<String> {
873 Err(DeviceError::UnsupportedDevice(
874 "IBM Quantum support not enabled".to_string(),
875 ))
876 }
877
878 pub async fn get_job_status(&self, _job_id: &str) -> DeviceResult<IBMJobStatus> {
879 Err(DeviceError::UnsupportedDevice(
880 "IBM Quantum support not enabled".to_string(),
881 ))
882 }
883
884 pub async fn get_job_result(&self, _job_id: &str) -> DeviceResult<IBMJobResult> {
885 Err(DeviceError::UnsupportedDevice(
886 "IBM Quantum support not enabled".to_string(),
887 ))
888 }
889
890 pub async fn wait_for_job(
891 &self,
892 _job_id: &str,
893 _timeout_secs: Option<u64>,
894 ) -> DeviceResult<IBMJobResult> {
895 Err(DeviceError::UnsupportedDevice(
896 "IBM Quantum support not enabled".to_string(),
897 ))
898 }
899
900 pub async fn submit_circuits_parallel(
901 &self,
902 _backend_name: &str,
903 _configs: Vec<IBMCircuitConfig>,
904 ) -> DeviceResult<Vec<String>> {
905 Err(DeviceError::UnsupportedDevice(
906 "IBM Quantum support not enabled".to_string(),
907 ))
908 }
909
910 pub fn circuit_to_qasm<const N: usize>(
911 _circuit: &Circuit<N>,
912 _initial_layout: Option<std::collections::HashMap<String, usize>>,
913 ) -> DeviceResult<String> {
914 Err(DeviceError::UnsupportedDevice(
915 "IBM Quantum support not enabled".to_string(),
916 ))
917 }
918}