1use crate::builder::Circuit;
7use crate::optimization::passes::OptimizationPassExt;
8use crate::optimization::{CircuitMetrics, CostModel, OptimizationPass};
9use crate::routing::CouplingMap;
10use quantrs2_core::gate::single::{PauliX, PauliY};
11use quantrs2_core::{
12 error::{QuantRS2Error, QuantRS2Result},
13 gate::GateOp,
14 qubit::QubitId,
15};
16use serde::{Deserialize, Serialize};
17use std::collections::{HashMap, VecDeque};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct NoiseModel {
22 pub single_qubit_errors: HashMap<usize, f64>,
24 pub two_qubit_errors: HashMap<(usize, usize), f64>,
26 pub t1_times: HashMap<usize, f64>,
28 pub t2_times: HashMap<usize, f64>,
30 pub readout_fidelities: HashMap<usize, f64>,
32 pub gate_times: HashMap<String, f64>,
34 pub crosstalk_matrix: Option<Vec<Vec<f64>>>,
36}
37
38impl NoiseModel {
39 #[must_use]
41 pub fn new() -> Self {
42 Self {
43 single_qubit_errors: HashMap::new(),
44 two_qubit_errors: HashMap::new(),
45 t1_times: HashMap::new(),
46 t2_times: HashMap::new(),
47 readout_fidelities: HashMap::new(),
48 gate_times: HashMap::new(),
49 crosstalk_matrix: None,
50 }
51 }
52
53 #[must_use]
55 pub fn uniform(num_qubits: usize) -> Self {
56 let mut model = Self::new();
57
58 let single_error = 1e-3;
60 let two_qubit_error = 1e-2;
61 let t1 = 100.0; let t2 = 50.0; let readout_fidelity = 0.99;
64
65 for i in 0..num_qubits {
66 model.single_qubit_errors.insert(i, single_error);
67 model.t1_times.insert(i, t1);
68 model.t2_times.insert(i, t2);
69 model.readout_fidelities.insert(i, readout_fidelity);
70
71 for j in (i + 1)..num_qubits {
72 model.two_qubit_errors.insert((i, j), two_qubit_error);
73 }
74 }
75
76 model.gate_times.insert("H".to_string(), 20.0);
78 model.gate_times.insert("X".to_string(), 20.0);
79 model.gate_times.insert("Y".to_string(), 20.0);
80 model.gate_times.insert("Z".to_string(), 0.0); model.gate_times.insert("S".to_string(), 0.0);
82 model.gate_times.insert("T".to_string(), 0.0);
83 model.gate_times.insert("CNOT".to_string(), 200.0);
84 model.gate_times.insert("CZ".to_string(), 200.0);
85 model.gate_times.insert("SWAP".to_string(), 600.0); model
88 }
89
90 #[must_use]
92 pub fn ibm_like(num_qubits: usize) -> Self {
93 let mut model = Self::new();
94
95 for i in 0..num_qubits {
97 model.single_qubit_errors.insert(i, 1e-4); model.t1_times.insert(i, (i as f64).mul_add(10.0, 100.0)); model.t2_times.insert(i, (i as f64).mul_add(5.0, 80.0)); model
101 .readout_fidelities
102 .insert(i, 0.95 + (i as f64 * 0.01).min(0.04));
103
104 for j in (i + 1)..num_qubits {
105 let error = if (i as isize - j as isize).abs() == 1 {
107 5e-3 } else {
109 1e-2 };
111 model.two_qubit_errors.insert((i, j), error);
112 }
113 }
114
115 model.gate_times.insert("H".to_string(), 35.0);
117 model.gate_times.insert("X".to_string(), 35.0);
118 model.gate_times.insert("Y".to_string(), 35.0);
119 model.gate_times.insert("Z".to_string(), 0.0);
120 model.gate_times.insert("S".to_string(), 0.0);
121 model.gate_times.insert("T".to_string(), 0.0);
122 model.gate_times.insert("CNOT".to_string(), 500.0);
123 model.gate_times.insert("CZ".to_string(), 300.0);
124 model.gate_times.insert("SWAP".to_string(), 1500.0);
125
126 model
127 }
128
129 #[must_use]
131 pub fn single_qubit_error(&self, qubit: usize) -> f64 {
132 self.single_qubit_errors
133 .get(&qubit)
134 .copied()
135 .unwrap_or(1e-3)
136 }
137
138 #[must_use]
140 pub fn two_qubit_error(&self, q1: usize, q2: usize) -> f64 {
141 let key = (q1.min(q2), q1.max(q2));
142 self.two_qubit_errors.get(&key).copied().unwrap_or(1e-2)
143 }
144
145 #[must_use]
147 pub fn t1_time(&self, qubit: usize) -> f64 {
148 self.t1_times.get(&qubit).copied().unwrap_or(100.0)
149 }
150
151 #[must_use]
153 pub fn t2_time(&self, qubit: usize) -> f64 {
154 self.t2_times.get(&qubit).copied().unwrap_or(50.0)
155 }
156
157 #[must_use]
159 pub fn gate_time(&self, gate_name: &str) -> f64 {
160 self.gate_times.get(gate_name).copied().unwrap_or(100.0)
161 }
162
163 pub fn gate_error_probability(&self, gate: &dyn GateOp) -> f64 {
165 let qubits = gate.qubits();
166 match qubits.len() {
167 1 => self.single_qubit_error(qubits[0].id() as usize),
168 2 => self.two_qubit_error(qubits[0].id() as usize, qubits[1].id() as usize),
169 _ => 0.1, }
171 }
172}
173
174impl Default for NoiseModel {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[derive(Debug, Clone)]
182pub struct NoiseAwareCostModel {
183 noise_model: NoiseModel,
184 coupling_map: Option<CouplingMap>,
185 pub error_weight: f64,
187 pub time_weight: f64,
189 pub coherence_weight: f64,
191}
192
193impl NoiseAwareCostModel {
194 #[must_use]
196 pub const fn new(noise_model: NoiseModel) -> Self {
197 Self {
198 noise_model,
199 coupling_map: None,
200 error_weight: 1000.0,
201 time_weight: 1.0,
202 coherence_weight: 100.0,
203 }
204 }
205
206 #[must_use]
208 pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
209 self.coupling_map = Some(coupling_map);
210 self
211 }
212
213 pub fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
215 let error_prob = self.noise_model.gate_error_probability(gate);
216 let exec_time = self.noise_model.gate_time(gate.name());
217
218 let mut cost = self
220 .error_weight
221 .mul_add(error_prob, self.time_weight * exec_time);
222
223 if exec_time > 100.0 {
225 let qubits = gate.qubits();
226 for qubit in qubits {
227 let t2 = self.noise_model.t2_time(qubit.id() as usize);
228 let coherence_penalty = exec_time / (t2 * 1000.0); cost += self.coherence_weight * coherence_penalty;
230 }
231 }
232
233 cost
234 }
235
236 #[must_use]
238 pub fn circuit_cost<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
239 let mut total_cost = 0.0;
240 let mut total_time = 0.0;
241
242 for gate in circuit.gates() {
244 total_cost += self.gate_cost(gate.as_ref());
245 total_time += self.noise_model.gate_time(gate.name());
246 }
247
248 if total_time > 0.0 {
250 for i in 0..N {
251 let t2 = self.noise_model.t2_time(i);
252 let coherence_penalty = total_time / (t2 * 1000.0);
253 total_cost += self.coherence_weight * coherence_penalty;
254 }
255 }
256
257 total_cost
258 }
259}
260
261impl CostModel for NoiseAwareCostModel {
262 fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
263 let error_prob = self.noise_model.gate_error_probability(gate);
264 let exec_time = self.noise_model.gate_time(gate.name());
265
266 let mut cost = self
268 .error_weight
269 .mul_add(error_prob, self.time_weight * exec_time);
270
271 if exec_time > 100.0 {
273 let qubits = gate.qubits();
274 for qubit in qubits {
275 let t2 = self.noise_model.t2_time(qubit.id() as usize);
276 let coherence_penalty = exec_time / (t2 * 1000.0); cost += self.coherence_weight * coherence_penalty;
278 }
279 }
280
281 cost
282 }
283
284 fn circuit_cost_from_gates(&self, gates: &[Box<dyn GateOp>]) -> f64 {
285 let mut total_cost = 0.0;
286 let mut total_time = 0.0;
287
288 for gate in gates {
290 total_cost += self.gate_cost(gate.as_ref());
291 total_time += self.noise_model.gate_time(gate.name());
292 }
293
294 total_cost
295 }
296
297 fn weights(&self) -> super::cost_model::CostWeights {
298 super::cost_model::CostWeights {
299 gate_count: 1.0,
300 execution_time: self.time_weight,
301 error_rate: self.error_weight,
302 circuit_depth: 1.0,
303 }
304 }
305
306 fn is_native(&self, gate: &dyn GateOp) -> bool {
307 matches!(
309 gate.name(),
310 "H" | "X" | "Y" | "Z" | "S" | "T" | "CNOT" | "CZ"
311 )
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct CoherenceOptimization {
318 noise_model: NoiseModel,
319 max_parallel_gates: usize,
320}
321
322impl CoherenceOptimization {
323 #[must_use]
325 pub const fn new(noise_model: NoiseModel) -> Self {
326 Self {
327 noise_model,
328 max_parallel_gates: 10,
329 }
330 }
331
332 fn find_parallel_gates<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<Vec<usize>> {
334 let gates = circuit.gates();
335 let mut parallel_groups = Vec::new();
336 let mut used_qubits = vec![false; N];
337 let mut current_group = Vec::new();
338
339 for (i, gate) in gates.iter().enumerate() {
340 let gate_qubits: Vec<_> = gate.qubits().iter().map(|q| q.id() as usize).collect();
341
342 let conflicts = gate_qubits.iter().any(|&q| used_qubits[q]);
344
345 if conflicts || current_group.len() >= self.max_parallel_gates {
346 if !current_group.is_empty() {
348 parallel_groups.push(current_group);
349 current_group = Vec::new();
350 used_qubits.fill(false);
351 }
352 }
353
354 current_group.push(i);
356 for &q in &gate_qubits {
357 used_qubits[q] = true;
358 }
359 }
360
361 if !current_group.is_empty() {
362 parallel_groups.push(current_group);
363 }
364
365 parallel_groups
366 }
367}
368
369impl OptimizationPass for CoherenceOptimization {
370 fn name(&self) -> &'static str {
371 "CoherenceOptimization"
372 }
373
374 fn apply_to_gates(
375 &self,
376 gates: Vec<Box<dyn GateOp>>,
377 _cost_model: &dyn CostModel,
378 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
379 if gates.is_empty() {
380 return Ok(gates);
381 }
382
383 let n = gates.len();
384
385 let gate_qubits: Vec<Vec<usize>> = gates
388 .iter()
389 .map(|g| g.qubits().iter().map(|q| q.id() as usize).collect())
390 .collect();
391
392 let mut predecessors: Vec<Vec<usize>> = vec![Vec::new(); n];
394 let max_qubit = gate_qubits
396 .iter()
397 .flat_map(|qs| qs.iter().copied())
398 .max()
399 .unwrap_or(0);
400 let mut last_gate_on_qubit: Vec<Option<usize>> = vec![None; max_qubit + 1];
401
402 for (i, qubits) in gate_qubits.iter().enumerate() {
403 for &q in qubits {
404 if let Some(prev) = last_gate_on_qubit[q] {
405 predecessors[i].push(prev);
406 }
407 }
408 for &q in qubits {
409 last_gate_on_qubit[q] = Some(i);
410 }
411 }
412
413 for preds in &mut predecessors {
415 preds.sort_unstable();
416 preds.dedup();
417 }
418
419 let mut in_degree: Vec<usize> = vec![0; n];
421 let mut successors: Vec<Vec<usize>> = vec![Vec::new(); n];
422 for (i, preds) in predecessors.iter().enumerate() {
423 in_degree[i] = preds.len();
424 for &p in preds {
425 successors[p].push(i);
426 }
427 }
428
429 let mut level: Vec<usize> = vec![0; n];
431 let mut ready: VecDeque<usize> = VecDeque::new();
432 for (i, °) in in_degree.iter().enumerate() {
433 if deg == 0 {
434 ready.push_back(i);
435 }
436 }
437
438 let mut remaining_in_degree = in_degree.clone();
439 let mut topo_order: Vec<usize> = Vec::with_capacity(n);
440
441 while let Some(node) = ready.pop_front() {
442 topo_order.push(node);
443 for &succ in &successors[node] {
444 let new_level = level[node] + 1;
445 if new_level > level[succ] {
446 level[succ] = new_level;
447 }
448 remaining_in_degree[succ] -= 1;
449 if remaining_in_degree[succ] == 0 {
450 ready.push_back(succ);
451 }
452 }
453 }
454
455 if topo_order.len() != n {
457 return Ok(gates);
458 }
459
460 let max_level = level.iter().copied().max().unwrap_or(0);
462 let mut levels: Vec<Vec<usize>> = vec![Vec::new(); max_level + 1];
463 for (i, &lv) in level.iter().enumerate() {
464 levels[lv].push(i);
465 }
466
467 for group in &mut levels {
469 group.sort_by_key(|&i| gate_qubits[i].iter().copied().min().unwrap_or(usize::MAX));
470 }
471
472 let mut result: Vec<Box<dyn GateOp>> = Vec::with_capacity(n);
474 for group in levels {
475 for idx in group {
476 result.push(gates[idx].clone_gate());
477 }
478 }
479
480 Ok(result)
481 }
482
483 fn should_apply(&self) -> bool {
484 true
485 }
486}
487
488#[derive(Debug, Clone)]
490pub struct NoiseAwareMapping {
491 noise_model: NoiseModel,
492 coupling_map: CouplingMap,
493}
494
495impl NoiseAwareMapping {
496 #[must_use]
498 pub const fn new(noise_model: NoiseModel, coupling_map: CouplingMap) -> Self {
499 Self {
500 noise_model,
501 coupling_map,
502 }
503 }
504
505 fn score_assignment(&self, logical_qubits: &[usize], physical_qubits: &[usize]) -> f64 {
507 let mut score = 0.0;
508
509 for (&logical, &physical) in logical_qubits.iter().zip(physical_qubits.iter()) {
511 score += 1.0 / (1.0 + self.noise_model.single_qubit_error(physical));
512 }
513
514 for i in 0..logical_qubits.len() {
516 for j in (i + 1)..logical_qubits.len() {
517 let p1 = physical_qubits[i];
518 let p2 = physical_qubits[j];
519
520 if self.coupling_map.are_connected(p1, p2) {
521 let error = self.noise_model.two_qubit_error(p1, p2);
522 score += 1.0 / (1.0 + error);
523 }
524 }
525 }
526
527 score
528 }
529}
530
531impl OptimizationPass for NoiseAwareMapping {
532 fn name(&self) -> &'static str {
533 "NoiseAwareMapping"
534 }
535
536 fn apply_to_gates(
537 &self,
538 gates: Vec<Box<dyn GateOp>>,
539 _cost_model: &dyn CostModel,
540 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
541 if gates.is_empty() {
542 return Ok(gates);
543 }
544
545 let mut logical_qubit_set: std::collections::HashSet<usize> =
547 std::collections::HashSet::new();
548 for gate in &gates {
549 for q in gate.qubits() {
550 logical_qubit_set.insert(q.id() as usize);
551 }
552 }
553 if logical_qubit_set.is_empty() {
554 return Ok(gates);
555 }
556
557 let mut interaction_count: HashMap<(usize, usize), usize> = HashMap::new();
559 for gate in &gates {
560 let qubits = gate.qubits();
561 if qubits.len() == 2 {
562 let a = qubits[0].id() as usize;
563 let b = qubits[1].id() as usize;
564 let key = (a.min(b), a.max(b));
565 *interaction_count.entry(key).or_insert(0) += 1;
566 }
567 }
568
569 let mut logical_qubits: Vec<usize> = logical_qubit_set.into_iter().collect();
571 logical_qubits.sort_unstable();
572 let mut logical_degree: HashMap<usize, usize> = HashMap::new();
573 for (&(a, b), &count) in &interaction_count {
574 *logical_degree.entry(a).or_insert(0) += count;
575 *logical_degree.entry(b).or_insert(0) += count;
576 }
577
578 logical_qubits.sort_by(|&a, &b| {
580 let da = logical_degree.get(&a).copied().unwrap_or(0);
581 let db = logical_degree.get(&b).copied().unwrap_or(0);
582 db.cmp(&da)
583 });
584
585 let mut physical_qubits: Vec<usize> = self
587 .noise_model
588 .single_qubit_errors
589 .keys()
590 .copied()
591 .collect();
592 if physical_qubits.is_empty() {
593 return Ok(gates);
594 }
595 physical_qubits.sort_by(|&a, &b| {
596 let ea = self.noise_model.single_qubit_error(a);
597 let eb = self.noise_model.single_qubit_error(b);
598 ea.partial_cmp(&eb).unwrap_or(std::cmp::Ordering::Equal)
599 });
600
601 let mut logical_to_physical: HashMap<usize, usize> = HashMap::new();
603 for (i, &logical_q) in logical_qubits.iter().enumerate() {
604 if i < physical_qubits.len() {
605 logical_to_physical.insert(logical_q, physical_qubits[i]);
606 } else {
607 logical_to_physical.insert(logical_q, logical_q);
609 }
610 }
611
612 let _ = logical_to_physical; Ok(gates)
627 }
628
629 fn should_apply(&self) -> bool {
630 true
631 }
632}
633
634#[derive(Debug, Clone)]
636pub struct DynamicalDecoupling {
637 noise_model: NoiseModel,
638 pub min_idle_time: f64,
640 pub sequence_type: DecouplingSequence,
642}
643
644#[derive(Debug, Clone)]
646pub enum DecouplingSequence {
647 XY4,
649 CPMG,
651 XY8,
653}
654
655impl DynamicalDecoupling {
656 #[must_use]
658 pub const fn new(noise_model: NoiseModel) -> Self {
659 Self {
660 noise_model,
661 min_idle_time: 1000.0, sequence_type: DecouplingSequence::XY4,
663 }
664 }
665
666 fn analyze_idle_times<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<(usize, f64)> {
668 let mut idle_times = Vec::new();
669
670 for (i, gate) in circuit.gates().iter().enumerate() {
672 let exec_time = self.noise_model.gate_time(gate.name());
673
674 if i + 1 < circuit.num_gates() {
676 idle_times.push((i, 500.0)); }
679 }
680
681 idle_times
682 }
683}
684
685impl OptimizationPass for DynamicalDecoupling {
686 fn name(&self) -> &'static str {
687 "DynamicalDecoupling"
688 }
689
690 fn apply_to_gates(
691 &self,
692 gates: Vec<Box<dyn GateOp>>,
693 _cost_model: &dyn CostModel,
694 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
695 if gates.is_empty() {
696 return Ok(gates);
697 }
698
699 let mut all_qubits: std::collections::HashSet<usize> = std::collections::HashSet::new();
701 for gate in &gates {
702 for q in gate.qubits() {
703 all_qubits.insert(q.id() as usize);
704 }
705 }
706
707 let sequence_pattern: &[&str] = match self.sequence_type {
710 DecouplingSequence::XY4 => &["X", "Y", "X", "Y"],
711 DecouplingSequence::CPMG => &["X", "X"],
712 DecouplingSequence::XY8 => &["X", "Y", "X", "Y", "Y", "X", "Y", "X"],
713 };
714
715 let n = gates.len();
729 let gate_qubit_ids: Vec<Vec<usize>> = gates
730 .iter()
731 .map(|g| g.qubits().iter().map(|q| q.id() as usize).collect())
732 .collect();
733
734 let max_qubit = all_qubits.iter().copied().max().unwrap_or(0);
736 let mut qubit_schedule: Vec<Vec<usize>> = vec![Vec::new(); max_qubit + 1];
737 for (i, qubits) in gate_qubit_ids.iter().enumerate() {
738 for &q in qubits {
739 qubit_schedule[q].push(i);
740 }
741 }
742
743 let default_gate_time = 50.0_f64; let mut insertions: Vec<(usize, usize)> = Vec::new(); for q in 0..=max_qubit {
752 let schedule = &qubit_schedule[q];
753 if schedule.len() < 2 {
754 continue;
755 }
756 for window in schedule.windows(2) {
757 let (prev_idx, next_idx) = (window[0], window[1]);
758 let mut idle_time = 0.0_f64;
761 for between in (prev_idx + 1)..next_idx {
762 let gt = self.noise_model.gate_time(gates[between].name());
763 idle_time += if gt > 0.0 { gt } else { default_gate_time };
764 }
765 let prev_time = self.noise_model.gate_time(gates[prev_idx].name());
767 idle_time += if prev_time > 0.0 {
768 prev_time
769 } else {
770 default_gate_time
771 };
772
773 if idle_time >= self.min_idle_time {
774 let pulse_time = 20.0_f64 * sequence_pattern.len() as f64;
776 if pulse_time <= idle_time {
777 insertions.push((next_idx, q));
778 }
779 }
780 }
781 }
782
783 if insertions.is_empty() {
784 return Ok(gates);
785 }
786
787 insertions.sort_by_key(|&(idx, _)| idx);
789
790 let mut result: Vec<Box<dyn GateOp>> =
793 Vec::with_capacity(n + insertions.len() * sequence_pattern.len());
794 let mut insert_iter = insertions.iter().peekable();
795
796 for i in 0..n {
797 while let Some(&&(ins_idx, qubit_id)) = insert_iter.peek() {
799 if ins_idx != i {
800 break;
801 }
802 insert_iter.next();
803 let target = QubitId::new(qubit_id as u32);
804 for &pulse in sequence_pattern {
805 let gate: Box<dyn GateOp> = match pulse {
806 "X" => Box::new(PauliX { target }),
807 "Y" => Box::new(PauliY { target }),
808 _ => Box::new(PauliX { target }),
809 };
810 result.push(gate);
811 }
812 }
813 result.push(gates[i].clone_gate());
814 }
815
816 Ok(result)
817 }
818
819 fn should_apply(&self) -> bool {
820 true
821 }
822}
823
824#[derive(Debug)]
826pub struct NoiseAwareOptimizer {
827 noise_model: NoiseModel,
828 coupling_map: Option<CouplingMap>,
829 cost_model: NoiseAwareCostModel,
830}
831
832impl NoiseAwareOptimizer {
833 #[must_use]
835 pub fn new(noise_model: NoiseModel) -> Self {
836 let cost_model = NoiseAwareCostModel::new(noise_model.clone());
837
838 Self {
839 noise_model,
840 coupling_map: None,
841 cost_model,
842 }
843 }
844
845 #[must_use]
847 pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
848 self.cost_model = self.cost_model.with_coupling_map(coupling_map.clone());
849 self.coupling_map = Some(coupling_map);
850 self
851 }
852
853 #[must_use]
855 pub fn get_passes(&self) -> Vec<Box<dyn OptimizationPass>> {
856 let mut passes: Vec<Box<dyn OptimizationPass>> = Vec::new();
857
858 passes.push(Box::new(CoherenceOptimization::new(
860 self.noise_model.clone(),
861 )));
862
863 if let Some(ref coupling_map) = self.coupling_map {
865 passes.push(Box::new(NoiseAwareMapping::new(
866 self.noise_model.clone(),
867 coupling_map.clone(),
868 )));
869 }
870
871 passes.push(Box::new(DynamicalDecoupling::new(self.noise_model.clone())));
873
874 passes
875 }
876
877 pub fn optimize<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<Circuit<N>> {
895 let mut gates: Vec<Box<dyn GateOp>> =
897 circuit.gates().iter().map(|g| g.clone_gate()).collect();
898
899 let passes = self.get_passes();
901 for pass in &passes {
902 if pass.should_apply() {
903 gates = pass.apply_to_gates(gates, &self.cost_model)?;
904 }
905 }
906
907 Circuit::<N>::from_gates(gates)
912 }
913
914 #[must_use]
916 pub fn estimate_fidelity<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
917 let mut total_error_prob = 0.0;
918
919 for gate in circuit.gates() {
920 let error_prob = self.noise_model.gate_error_probability(gate.as_ref());
921 total_error_prob += error_prob;
922 }
923
924 (1.0 - total_error_prob).max(0.0)
926 }
927
928 #[must_use]
930 pub const fn noise_model(&self) -> &NoiseModel {
931 &self.noise_model
932 }
933
934 #[must_use]
936 pub const fn cost_model(&self) -> &NoiseAwareCostModel {
937 &self.cost_model
938 }
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use quantrs2_core::gate::{multi::CNOT, single::Hadamard};
945
946 #[test]
947 fn test_noise_model_creation() {
948 let model = NoiseModel::uniform(4);
949
950 assert_eq!(model.single_qubit_error(0), 1e-3);
951 assert_eq!(model.two_qubit_error(0, 1), 1e-2);
952 assert_eq!(model.t1_time(0), 100.0);
953 assert_eq!(model.gate_time("CNOT"), 200.0);
954 }
955
956 #[test]
957 fn test_ibm_noise_model() {
958 let model = NoiseModel::ibm_like(3);
959
960 assert_eq!(model.single_qubit_error(0), 1e-4);
961 assert_eq!(model.gate_time("H"), 35.0);
962 assert!(model.t1_time(1) > model.t1_time(0));
963 }
964
965 #[test]
966 fn test_noise_aware_cost_model() {
967 let noise_model = NoiseModel::uniform(4);
968 let cost_model = NoiseAwareCostModel::new(noise_model);
969
970 let h_gate = Hadamard { target: QubitId(0) };
971 let cnot_gate = CNOT {
972 control: QubitId(0),
973 target: QubitId(1),
974 };
975
976 let h_cost = cost_model.gate_cost(&h_gate);
977 let cnot_cost = cost_model.gate_cost(&cnot_gate);
978
979 assert!(cnot_cost > h_cost);
981 }
982
983 #[test]
984 fn test_coherence_optimization() {
985 let noise_model = NoiseModel::uniform(4);
986 let optimizer = CoherenceOptimization::new(noise_model.clone());
987
988 let mut circuit = Circuit::<4>::new();
989 circuit
990 .add_gate(Hadamard { target: QubitId(0) })
991 .expect("Failed to add Hadamard gate");
992 circuit
993 .add_gate(CNOT {
994 control: QubitId(0),
995 target: QubitId(1),
996 })
997 .expect("Failed to add CNOT gate");
998
999 let cost_model = NoiseAwareCostModel::new(noise_model);
1000 let result = optimizer.apply(&circuit, &cost_model);
1001 assert!(result.is_ok());
1002 }
1003
1004 #[test]
1005 fn test_noise_aware_optimizer() {
1006 let noise_model = NoiseModel::uniform(4);
1007 let optimizer = NoiseAwareOptimizer::new(noise_model);
1008
1009 let mut circuit = Circuit::<4>::new();
1010 circuit
1011 .add_gate(Hadamard { target: QubitId(0) })
1012 .expect("Failed to add Hadamard gate");
1013 circuit
1014 .add_gate(CNOT {
1015 control: QubitId(0),
1016 target: QubitId(1),
1017 })
1018 .expect("Failed to add CNOT gate");
1019
1020 let optimized = optimizer
1021 .optimize(&circuit)
1022 .expect("Optimization should succeed");
1023 let fidelity = optimizer.estimate_fidelity(&optimized);
1024
1025 assert!(fidelity > 0.9); assert!(fidelity <= 1.0);
1027 }
1028}