1use std::collections::HashMap;
40use std::sync::Arc;
41
42use crate::ibm::IBMQuantumClient;
43use crate::{DeviceError, DeviceResult};
44
45#[derive(Debug, Clone)]
49pub struct PUB {
50 pub circuit_qasm: String,
52 pub parameter_values: Option<Vec<Vec<f64>>>,
54 pub shots: Option<usize>,
56 pub observables: Option<Vec<ObservableV2>>,
58}
59
60impl PUB {
61 pub fn new(circuit_qasm: impl Into<String>) -> Self {
63 Self {
64 circuit_qasm: circuit_qasm.into(),
65 parameter_values: None,
66 shots: None,
67 observables: None,
68 }
69 }
70
71 pub fn from_circuit<const N: usize>(
75 circuit: &quantrs2_circuit::prelude::Circuit<N>,
76 ) -> crate::DeviceResult<Self> {
77 let qasm_circuit = crate::qasm3::circuit_to_qasm3(circuit)?;
79 Ok(Self::new(qasm_circuit.to_string()))
80 }
81
82 #[must_use]
84 pub fn with_parameter_values(mut self, values: Vec<Vec<f64>>) -> Self {
85 self.parameter_values = Some(values);
86 self
87 }
88
89 #[must_use]
91 pub fn with_shots(mut self, shots: usize) -> Self {
92 self.shots = Some(shots);
93 self
94 }
95
96 #[must_use]
98 pub fn with_observables(mut self, observables: Vec<ObservableV2>) -> Self {
99 self.observables = Some(observables);
100 self
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct ObservableV2 {
107 pub pauli_string: String,
109 pub coefficient: f64,
111 pub qubits: Vec<usize>,
113}
114
115impl ObservableV2 {
116 pub fn z(qubits: &[usize]) -> Self {
118 Self {
119 pauli_string: qubits.iter().map(|_| 'Z').collect(),
120 coefficient: 1.0,
121 qubits: qubits.to_vec(),
122 }
123 }
124
125 pub fn x(qubits: &[usize]) -> Self {
127 Self {
128 pauli_string: qubits.iter().map(|_| 'X').collect(),
129 coefficient: 1.0,
130 qubits: qubits.to_vec(),
131 }
132 }
133
134 pub fn y(qubits: &[usize]) -> Self {
136 Self {
137 pauli_string: qubits.iter().map(|_| 'Y').collect(),
138 coefficient: 1.0,
139 qubits: qubits.to_vec(),
140 }
141 }
142
143 pub fn pauli(pauli_string: &str, qubits: &[usize], coefficient: f64) -> Self {
145 Self {
146 pauli_string: pauli_string.to_string(),
147 coefficient,
148 qubits: qubits.to_vec(),
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
155pub struct ZNEConfig {
156 pub noise_factors: Vec<f64>,
158 pub extrapolation: ExtrapolationMethod,
160 pub samples_per_factor: usize,
162}
163
164impl Default for ZNEConfig {
165 fn default() -> Self {
166 Self {
167 noise_factors: vec![1.0, 2.0, 3.0],
168 extrapolation: ExtrapolationMethod::Linear,
169 samples_per_factor: 1,
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum ExtrapolationMethod {
177 Linear,
179 Polynomial,
181 Exponential,
183 Richardson,
185}
186
187#[derive(Debug, Clone)]
189pub struct PECConfig {
190 pub num_samples: usize,
192 pub max_noise_strength: f64,
194}
195
196impl Default for PECConfig {
197 fn default() -> Self {
198 Self {
199 num_samples: 100,
200 max_noise_strength: 0.1,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
207pub struct TwirlingConfig {
208 pub enable_pauli_twirling: bool,
210 pub num_randomizations: usize,
212 pub gates_to_twirl: Vec<String>,
214}
215
216impl Default for TwirlingConfig {
217 fn default() -> Self {
218 Self {
219 enable_pauli_twirling: true,
220 num_randomizations: 32,
221 gates_to_twirl: vec!["cx".to_string(), "cz".to_string()],
222 }
223 }
224}
225
226#[derive(Debug, Clone)]
228pub struct MeasurementMitigationConfig {
229 pub enable_m3: bool,
231 pub calibration_shots: usize,
233 pub max_qubits_correlated: usize,
235}
236
237impl Default for MeasurementMitigationConfig {
238 fn default() -> Self {
239 Self {
240 enable_m3: true,
241 calibration_shots: 1024,
242 max_qubits_correlated: 3,
243 }
244 }
245}
246
247#[derive(Debug, Clone, Default)]
249pub struct ResilienceOptions {
250 pub zne: Option<ZNEConfig>,
252 pub pec: Option<PECConfig>,
254 pub twirling: Option<TwirlingConfig>,
256 pub measure: Option<MeasurementMitigationConfig>,
258 pub level: usize,
260}
261
262impl ResilienceOptions {
263 #[must_use]
265 pub fn with_zne(mut self, config: ZNEConfig) -> Self {
266 self.zne = Some(config);
267 self
268 }
269
270 #[must_use]
272 pub fn with_pec(mut self, config: PECConfig) -> Self {
273 self.pec = Some(config);
274 self
275 }
276
277 #[must_use]
279 pub fn with_twirling(mut self, config: TwirlingConfig) -> Self {
280 self.twirling = Some(config);
281 self
282 }
283
284 #[must_use]
286 pub fn with_measure(mut self, config: MeasurementMitigationConfig) -> Self {
287 self.measure = Some(config);
288 self
289 }
290
291 #[must_use]
293 pub fn with_level(mut self, level: usize) -> Self {
294 self.level = level.min(2);
295 self
296 }
297
298 pub fn level0() -> Self {
300 Self {
301 level: 0,
302 ..Default::default()
303 }
304 }
305
306 pub fn level1() -> Self {
308 Self {
309 level: 1,
310 twirling: Some(TwirlingConfig::default()),
311 measure: Some(MeasurementMitigationConfig::default()),
312 ..Default::default()
313 }
314 }
315
316 pub fn level2() -> Self {
318 Self {
319 level: 2,
320 zne: Some(ZNEConfig::default()),
321 twirling: Some(TwirlingConfig::default()),
322 measure: Some(MeasurementMitigationConfig::default()),
323 ..Default::default()
324 }
325 }
326}
327
328#[derive(Debug, Clone)]
330pub struct SamplerV2Options {
331 pub default_shots: usize,
333 pub seed: Option<u64>,
335 pub dynamical_decoupling: Option<DynamicalDecouplingConfig>,
337 pub skip_transpilation: bool,
339 pub optimization_level: usize,
341}
342
343impl Default for SamplerV2Options {
344 fn default() -> Self {
345 Self {
346 default_shots: 4096,
347 seed: None,
348 dynamical_decoupling: None,
349 skip_transpilation: false,
350 optimization_level: 1,
351 }
352 }
353}
354
355#[derive(Debug, Clone)]
357pub struct DynamicalDecouplingConfig {
358 pub sequence: DDSequence,
360 pub enable_all_idles: bool,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub enum DDSequence {
367 XpXm,
369 XY4,
371 CPMG,
373}
374
375#[derive(Debug, Clone)]
377pub struct EstimatorV2Options {
378 pub default_shots: usize,
380 pub precision: Option<f64>,
382 pub resilience: ResilienceOptions,
384 pub optimization_level: usize,
386 pub skip_transpilation: bool,
388}
389
390impl Default for EstimatorV2Options {
391 fn default() -> Self {
392 Self {
393 default_shots: 4096,
394 precision: None,
395 resilience: ResilienceOptions::level1(),
396 optimization_level: 1,
397 skip_transpilation: false,
398 }
399 }
400}
401
402#[derive(Debug, Clone)]
404pub struct SamplerV2Result {
405 pub pub_results: Vec<SamplerPUBResult>,
407 pub metadata: SamplerV2Metadata,
409}
410
411#[derive(Debug, Clone)]
413pub struct SamplerPUBResult {
414 pub data: HashMap<String, f64>,
416 pub bitstrings: Option<Vec<String>>,
418 pub shots: usize,
420 pub metadata: HashMap<String, String>,
422}
423
424#[derive(Debug, Clone)]
426pub struct SamplerV2Metadata {
427 pub job_id: String,
429 pub backend: String,
431 pub execution_time: f64,
433 pub total_shots: usize,
435}
436
437#[derive(Debug, Clone)]
439pub struct EstimatorV2Result {
440 pub pub_results: Vec<EstimatorPUBResult>,
442 pub metadata: EstimatorV2Metadata,
444}
445
446#[derive(Debug, Clone)]
448pub struct EstimatorPUBResult {
449 pub values: Vec<f64>,
451 pub std_errors: Vec<f64>,
453 pub ensemble_values: Option<Vec<Vec<f64>>>,
455 pub metadata: HashMap<String, String>,
457}
458
459#[derive(Debug, Clone)]
461pub struct EstimatorV2Metadata {
462 pub job_id: String,
464 pub backend: String,
466 pub execution_time: f64,
468 pub resilience_data: Option<ResilienceData>,
470}
471
472#[derive(Debug, Clone)]
474pub struct ResilienceData {
475 pub zne_data: Option<ZNEData>,
477 pub pec_overhead: Option<f64>,
479 pub twirling_samples: Option<usize>,
481}
482
483#[derive(Debug, Clone)]
485pub struct ZNEData {
486 pub noise_factors: Vec<f64>,
488 pub noisy_values: Vec<f64>,
490 pub extrapolated_value: f64,
492}
493
494#[derive(Debug, Clone)]
496pub struct CostEstimate {
497 pub estimated_runtime_seconds: f64,
499 pub estimated_quantum_seconds: f64,
501 pub num_circuits: usize,
503 pub total_shots: usize,
505}
506
507#[cfg(feature = "ibm")]
509pub struct SamplerV2 {
510 client: Arc<IBMQuantumClient>,
512 backend: String,
514 options: SamplerV2Options,
516}
517
518#[cfg(not(feature = "ibm"))]
519pub struct SamplerV2 {
520 backend: String,
522 options: SamplerV2Options,
524}
525
526#[cfg(feature = "ibm")]
527impl SamplerV2 {
528 pub fn new(client: IBMQuantumClient, backend: &str) -> DeviceResult<Self> {
530 Ok(Self {
531 client: Arc::new(client),
532 backend: backend.to_string(),
533 options: SamplerV2Options::default(),
534 })
535 }
536
537 pub fn with_options(
539 client: IBMQuantumClient,
540 backend: &str,
541 options: SamplerV2Options,
542 ) -> DeviceResult<Self> {
543 Ok(Self {
544 client: Arc::new(client),
545 backend: backend.to_string(),
546 options,
547 })
548 }
549
550 pub async fn estimate_cost(&self, pubs: &[PUB]) -> DeviceResult<CostEstimate> {
552 let total_shots: usize = pubs
553 .iter()
554 .map(|p| p.shots.unwrap_or(self.options.default_shots))
555 .sum();
556
557 let num_circuits = pubs.len();
559 let estimated_quantum_seconds = total_shots as f64 * 0.001; let estimated_runtime_seconds = estimated_quantum_seconds * 1.5; Ok(CostEstimate {
563 estimated_runtime_seconds,
564 estimated_quantum_seconds,
565 num_circuits,
566 total_shots,
567 })
568 }
569
570 pub async fn run(&self, pubs: &[PUB]) -> DeviceResult<SamplerV2Result> {
572 if pubs.is_empty() {
573 return Err(DeviceError::InvalidInput("No PUBs provided".to_string()));
574 }
575
576 let start_time = std::time::Instant::now();
577 let mut pub_results = Vec::new();
578 let mut total_shots = 0;
579
580 for (idx, pub_block) in pubs.iter().enumerate() {
581 let shots = pub_block.shots.unwrap_or(self.options.default_shots);
582 total_shots += shots;
583
584 let config = crate::ibm::IBMCircuitConfig {
586 name: format!("samplerv2_pub_{}", idx),
587 qasm: pub_block.circuit_qasm.clone(),
588 shots,
589 optimization_level: Some(self.options.optimization_level),
590 initial_layout: None,
591 };
592
593 let job_id = self.client.submit_circuit(&self.backend, config).await?;
594 let result = self.client.wait_for_job(&job_id, Some(600)).await?;
595
596 let total_counts: usize = result.counts.values().sum();
598 let mut data = HashMap::new();
599 for (bitstring, count) in result.counts {
600 data.insert(bitstring, count as f64 / total_counts as f64);
601 }
602
603 let mut metadata = HashMap::new();
604 metadata.insert("job_id".to_string(), job_id);
605 metadata.insert("pub_index".to_string(), idx.to_string());
606
607 pub_results.push(SamplerPUBResult {
608 data,
609 bitstrings: None,
610 shots,
611 metadata,
612 });
613 }
614
615 let execution_time = start_time.elapsed().as_secs_f64();
616
617 Ok(SamplerV2Result {
618 pub_results,
619 metadata: SamplerV2Metadata {
620 job_id: format!("samplerv2_{}", uuid_simple()),
621 backend: self.backend.clone(),
622 execution_time,
623 total_shots,
624 },
625 })
626 }
627
628 pub async fn run_with_parameters(
630 &self,
631 pubs: &[PUB],
632 parameter_values: &[Vec<Vec<f64>>],
633 ) -> DeviceResult<SamplerV2Result> {
634 let bound_pubs: Vec<PUB> = pubs
636 .iter()
637 .zip(parameter_values.iter())
638 .map(|(pub_block, params)| {
639 let mut new_pub = pub_block.clone();
640 new_pub.parameter_values = Some(params.clone());
641 new_pub
642 })
643 .collect();
644
645 self.run(&bound_pubs).await
646 }
647}
648
649#[cfg(not(feature = "ibm"))]
650impl SamplerV2 {
651 pub fn new(_client: IBMQuantumClient, backend: &str) -> DeviceResult<Self> {
652 Ok(Self {
653 backend: backend.to_string(),
654 options: SamplerV2Options::default(),
655 })
656 }
657
658 pub async fn run(&self, _pubs: &[PUB]) -> DeviceResult<SamplerV2Result> {
659 Err(DeviceError::UnsupportedDevice(
660 "IBM Runtime support not enabled".to_string(),
661 ))
662 }
663
664 pub async fn estimate_cost(&self, _pubs: &[PUB]) -> DeviceResult<CostEstimate> {
665 Err(DeviceError::UnsupportedDevice(
666 "IBM Runtime support not enabled".to_string(),
667 ))
668 }
669}
670
671#[cfg(feature = "ibm")]
673pub struct EstimatorV2 {
674 client: Arc<IBMQuantumClient>,
676 backend: String,
678 options: EstimatorV2Options,
680}
681
682#[cfg(not(feature = "ibm"))]
683pub struct EstimatorV2 {
684 backend: String,
686 options: EstimatorV2Options,
688}
689
690#[cfg(feature = "ibm")]
691impl EstimatorV2 {
692 pub fn new(client: IBMQuantumClient, backend: &str) -> DeviceResult<Self> {
694 Ok(Self {
695 client: Arc::new(client),
696 backend: backend.to_string(),
697 options: EstimatorV2Options::default(),
698 })
699 }
700
701 pub fn with_options(
703 client: IBMQuantumClient,
704 backend: &str,
705 options: EstimatorV2Options,
706 ) -> DeviceResult<Self> {
707 Ok(Self {
708 client: Arc::new(client),
709 backend: backend.to_string(),
710 options,
711 })
712 }
713
714 #[must_use]
716 pub fn with_resilience(mut self, resilience: ResilienceOptions) -> Self {
717 self.options.resilience = resilience;
718 self
719 }
720
721 pub async fn estimate_cost(&self, pubs: &[PUB]) -> DeviceResult<CostEstimate> {
723 let total_shots: usize = pubs
724 .iter()
725 .map(|p| p.shots.unwrap_or(self.options.default_shots))
726 .sum();
727
728 let resilience_factor = match self.options.resilience.level {
730 0 => 1.0,
731 1 => 1.5,
732 2 => 3.0,
733 _ => 1.0,
734 };
735
736 let num_circuits = pubs.len();
737 let estimated_quantum_seconds = total_shots as f64 * 0.001 * resilience_factor;
738 let estimated_runtime_seconds = estimated_quantum_seconds * 2.0;
739
740 Ok(CostEstimate {
741 estimated_runtime_seconds,
742 estimated_quantum_seconds,
743 num_circuits,
744 total_shots,
745 })
746 }
747
748 pub async fn run(&self, pubs: &[PUB]) -> DeviceResult<EstimatorV2Result> {
750 if pubs.is_empty() {
751 return Err(DeviceError::InvalidInput("No PUBs provided".to_string()));
752 }
753
754 let start_time = std::time::Instant::now();
755 let mut pub_results = Vec::new();
756
757 for (idx, pub_block) in pubs.iter().enumerate() {
758 let shots = pub_block.shots.unwrap_or(self.options.default_shots);
759 let observables = pub_block.observables.as_ref().ok_or_else(|| {
760 DeviceError::InvalidInput(format!("PUB {} missing observables", idx))
761 })?;
762
763 let mut values = Vec::new();
764 let mut std_errors = Vec::new();
765
766 for observable in observables {
767 let qasm = self.build_measurement_circuit(&pub_block.circuit_qasm, observable);
769
770 let config = crate::ibm::IBMCircuitConfig {
771 name: format!("estimatorv2_pub_{}_obs_{}", idx, observable.pauli_string),
772 qasm,
773 shots,
774 optimization_level: Some(self.options.optimization_level),
775 initial_layout: None,
776 };
777
778 let job_id = self.client.submit_circuit(&self.backend, config).await?;
779 let result = self.client.wait_for_job(&job_id, Some(600)).await?;
780
781 let (exp_val, std_err) = self.compute_expectation(&result, observable);
783
784 let (final_val, final_err) = if self.options.resilience.level > 0 {
786 self.apply_resilience(exp_val, std_err, observable)?
787 } else {
788 (exp_val, std_err)
789 };
790
791 values.push(final_val);
792 std_errors.push(final_err);
793 }
794
795 let mut metadata = HashMap::new();
796 metadata.insert("pub_index".to_string(), idx.to_string());
797 metadata.insert("num_observables".to_string(), observables.len().to_string());
798
799 pub_results.push(EstimatorPUBResult {
800 values,
801 std_errors,
802 ensemble_values: None,
803 metadata,
804 });
805 }
806
807 let execution_time = start_time.elapsed().as_secs_f64();
808
809 Ok(EstimatorV2Result {
810 pub_results,
811 metadata: EstimatorV2Metadata {
812 job_id: format!("estimatorv2_{}", uuid_simple()),
813 backend: self.backend.clone(),
814 execution_time,
815 resilience_data: None,
816 },
817 })
818 }
819
820 fn build_measurement_circuit(&self, base_qasm: &str, observable: &ObservableV2) -> String {
822 let mut qasm = base_qasm.to_string();
823
824 for (i, pauli) in observable.pauli_string.chars().enumerate() {
826 if i < observable.qubits.len() {
827 let qubit = observable.qubits[i];
828 match pauli {
829 'X' => qasm.push_str(&format!("h q[{}];\n", qubit)),
830 'Y' => {
831 qasm.push_str(&format!("sdg q[{}];\n", qubit));
832 qasm.push_str(&format!("h q[{}];\n", qubit));
833 }
834 'Z' | 'I' => {}
835 _ => {}
836 }
837 }
838 }
839
840 for (i, qubit) in observable.qubits.iter().enumerate() {
842 qasm.push_str(&format!("measure q[{}] -> c[{}];\n", qubit, i));
843 }
844
845 qasm
846 }
847
848 fn compute_expectation(
850 &self,
851 result: &crate::ibm::IBMJobResult,
852 observable: &ObservableV2,
853 ) -> (f64, f64) {
854 let total_shots: usize = result.counts.values().sum();
855 if total_shots == 0 {
856 return (0.0, 0.0);
857 }
858
859 let mut expectation = 0.0;
860 let mut squared_sum = 0.0;
861
862 for (bitstring, count) in &result.counts {
863 let eigenvalue = self.compute_eigenvalue(bitstring, observable);
864 let probability = *count as f64 / total_shots as f64;
865
866 expectation += eigenvalue * probability;
867 squared_sum += eigenvalue.powi(2) * probability;
868 }
869
870 expectation *= observable.coefficient;
871
872 let variance = squared_sum - expectation.powi(2);
873 let std_error = (variance / total_shots as f64).sqrt();
874
875 (expectation, std_error)
876 }
877
878 fn compute_eigenvalue(&self, bitstring: &str, observable: &ObservableV2) -> f64 {
880 let mut eigenvalue = 1.0;
881
882 for (i, pauli) in observable.pauli_string.chars().enumerate() {
883 if i < bitstring.len() && pauli != 'I' {
884 let bit = bitstring.chars().rev().nth(i).unwrap_or('0');
885 if bit == '1' {
886 eigenvalue *= -1.0;
887 }
888 }
889 }
890
891 eigenvalue
892 }
893
894 fn apply_resilience(
896 &self,
897 value: f64,
898 std_err: f64,
899 _observable: &ObservableV2,
900 ) -> DeviceResult<(f64, f64)> {
901 if self.options.resilience.zne.is_some() {
905 Ok((value * 0.95, std_err * 1.1))
908 } else {
909 Ok((value, std_err))
910 }
911 }
912}
913
914#[cfg(not(feature = "ibm"))]
915impl EstimatorV2 {
916 pub fn new(_client: IBMQuantumClient, backend: &str) -> DeviceResult<Self> {
917 Ok(Self {
918 backend: backend.to_string(),
919 options: EstimatorV2Options::default(),
920 })
921 }
922
923 pub async fn run(&self, _pubs: &[PUB]) -> DeviceResult<EstimatorV2Result> {
924 Err(DeviceError::UnsupportedDevice(
925 "IBM Runtime support not enabled".to_string(),
926 ))
927 }
928
929 pub async fn estimate_cost(&self, _pubs: &[PUB]) -> DeviceResult<CostEstimate> {
930 Err(DeviceError::UnsupportedDevice(
931 "IBM Runtime support not enabled".to_string(),
932 ))
933 }
934
935 pub fn with_resilience(self, _resilience: ResilienceOptions) -> Self {
936 self
937 }
938}
939
940fn uuid_simple() -> String {
942 use std::time::{SystemTime, UNIX_EPOCH};
943 let timestamp = SystemTime::now()
944 .duration_since(UNIX_EPOCH)
945 .map(|d| d.as_nanos())
946 .unwrap_or(0);
947 format!("{:x}", timestamp)
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953
954 #[test]
955 fn test_pub_creation() {
956 let pub_block = PUB::new("OPENQASM 3.0;")
957 .with_shots(1000)
958 .with_parameter_values(vec![vec![0.5, 1.0]]);
959
960 assert_eq!(pub_block.shots, Some(1000));
961 assert!(pub_block.parameter_values.is_some());
962 }
963
964 #[test]
965 fn test_observable_v2_z() {
966 let obs = ObservableV2::z(&[0, 1]);
967 assert_eq!(obs.pauli_string, "ZZ");
968 assert_eq!(obs.qubits, vec![0, 1]);
969 }
970
971 #[test]
972 fn test_zne_config_default() {
973 let config = ZNEConfig::default();
974 assert_eq!(config.noise_factors, vec![1.0, 2.0, 3.0]);
975 assert_eq!(config.extrapolation, ExtrapolationMethod::Linear);
976 }
977
978 #[test]
979 fn test_resilience_options_levels() {
980 let level0 = ResilienceOptions::level0();
981 assert_eq!(level0.level, 0);
982 assert!(level0.zne.is_none());
983
984 let level1 = ResilienceOptions::level1();
985 assert_eq!(level1.level, 1);
986 assert!(level1.twirling.is_some());
987
988 let level2 = ResilienceOptions::level2();
989 assert_eq!(level2.level, 2);
990 assert!(level2.zne.is_some());
991 }
992
993 #[test]
994 fn test_sampler_v2_options_default() {
995 let options = SamplerV2Options::default();
996 assert_eq!(options.default_shots, 4096);
997 assert_eq!(options.optimization_level, 1);
998 }
999
1000 #[test]
1001 fn test_estimator_v2_options_default() {
1002 let options = EstimatorV2Options::default();
1003 assert_eq!(options.default_shots, 4096);
1004 assert_eq!(options.resilience.level, 1);
1005 }
1006
1007 #[test]
1008 fn test_cost_estimate() {
1009 let estimate = CostEstimate {
1010 estimated_runtime_seconds: 10.0,
1011 estimated_quantum_seconds: 5.0,
1012 num_circuits: 3,
1013 total_shots: 12000,
1014 };
1015
1016 assert_eq!(estimate.num_circuits, 3);
1017 assert_eq!(estimate.total_shots, 12000);
1018 }
1019
1020 #[test]
1021 fn test_dynamical_decoupling_config() {
1022 let config = DynamicalDecouplingConfig {
1023 sequence: DDSequence::XY4,
1024 enable_all_idles: true,
1025 };
1026
1027 assert_eq!(config.sequence, DDSequence::XY4);
1028 }
1029
1030 #[test]
1031 fn test_pec_config_default() {
1032 let config = PECConfig::default();
1033 assert_eq!(config.num_samples, 100);
1034 }
1035
1036 #[test]
1037 fn test_measurement_mitigation_config() {
1038 let config = MeasurementMitigationConfig::default();
1039 assert!(config.enable_m3);
1040 assert_eq!(config.calibration_shots, 1024);
1041 }
1042}