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