1use crate::builder::Circuit;
7use crate::optimization::passes::OptimizationPassExt;
8use crate::optimization::{CircuitMetrics, CostModel, OptimizationPass};
9use crate::routing::CouplingMap;
10use quantrs2_core::{
11 error::{QuantRS2Error, QuantRS2Result},
12 gate::GateOp,
13 qubit::QubitId,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct NoiseModel {
21 pub single_qubit_errors: HashMap<usize, f64>,
23 pub two_qubit_errors: HashMap<(usize, usize), f64>,
25 pub t1_times: HashMap<usize, f64>,
27 pub t2_times: HashMap<usize, f64>,
29 pub readout_fidelities: HashMap<usize, f64>,
31 pub gate_times: HashMap<String, f64>,
33 pub crosstalk_matrix: Option<Vec<Vec<f64>>>,
35}
36
37impl NoiseModel {
38 #[must_use]
40 pub fn new() -> Self {
41 Self {
42 single_qubit_errors: HashMap::new(),
43 two_qubit_errors: HashMap::new(),
44 t1_times: HashMap::new(),
45 t2_times: HashMap::new(),
46 readout_fidelities: HashMap::new(),
47 gate_times: HashMap::new(),
48 crosstalk_matrix: None,
49 }
50 }
51
52 #[must_use]
54 pub fn uniform(num_qubits: usize) -> Self {
55 let mut model = Self::new();
56
57 let single_error = 1e-3;
59 let two_qubit_error = 1e-2;
60 let t1 = 100.0; let t2 = 50.0; let readout_fidelity = 0.99;
63
64 for i in 0..num_qubits {
65 model.single_qubit_errors.insert(i, single_error);
66 model.t1_times.insert(i, t1);
67 model.t2_times.insert(i, t2);
68 model.readout_fidelities.insert(i, readout_fidelity);
69
70 for j in (i + 1)..num_qubits {
71 model.two_qubit_errors.insert((i, j), two_qubit_error);
72 }
73 }
74
75 model.gate_times.insert("H".to_string(), 20.0);
77 model.gate_times.insert("X".to_string(), 20.0);
78 model.gate_times.insert("Y".to_string(), 20.0);
79 model.gate_times.insert("Z".to_string(), 0.0); model.gate_times.insert("S".to_string(), 0.0);
81 model.gate_times.insert("T".to_string(), 0.0);
82 model.gate_times.insert("CNOT".to_string(), 200.0);
83 model.gate_times.insert("CZ".to_string(), 200.0);
84 model.gate_times.insert("SWAP".to_string(), 600.0); model
87 }
88
89 #[must_use]
91 pub fn ibm_like(num_qubits: usize) -> Self {
92 let mut model = Self::new();
93
94 for i in 0..num_qubits {
96 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
100 .readout_fidelities
101 .insert(i, 0.95 + (i as f64 * 0.01).min(0.04));
102
103 for j in (i + 1)..num_qubits {
104 let error = if (i as isize - j as isize).abs() == 1 {
106 5e-3 } else {
108 1e-2 };
110 model.two_qubit_errors.insert((i, j), error);
111 }
112 }
113
114 model.gate_times.insert("H".to_string(), 35.0);
116 model.gate_times.insert("X".to_string(), 35.0);
117 model.gate_times.insert("Y".to_string(), 35.0);
118 model.gate_times.insert("Z".to_string(), 0.0);
119 model.gate_times.insert("S".to_string(), 0.0);
120 model.gate_times.insert("T".to_string(), 0.0);
121 model.gate_times.insert("CNOT".to_string(), 500.0);
122 model.gate_times.insert("CZ".to_string(), 300.0);
123 model.gate_times.insert("SWAP".to_string(), 1500.0);
124
125 model
126 }
127
128 #[must_use]
130 pub fn single_qubit_error(&self, qubit: usize) -> f64 {
131 self.single_qubit_errors
132 .get(&qubit)
133 .copied()
134 .unwrap_or(1e-3)
135 }
136
137 #[must_use]
139 pub fn two_qubit_error(&self, q1: usize, q2: usize) -> f64 {
140 let key = (q1.min(q2), q1.max(q2));
141 self.two_qubit_errors.get(&key).copied().unwrap_or(1e-2)
142 }
143
144 #[must_use]
146 pub fn t1_time(&self, qubit: usize) -> f64 {
147 self.t1_times.get(&qubit).copied().unwrap_or(100.0)
148 }
149
150 #[must_use]
152 pub fn t2_time(&self, qubit: usize) -> f64 {
153 self.t2_times.get(&qubit).copied().unwrap_or(50.0)
154 }
155
156 #[must_use]
158 pub fn gate_time(&self, gate_name: &str) -> f64 {
159 self.gate_times.get(gate_name).copied().unwrap_or(100.0)
160 }
161
162 pub fn gate_error_probability(&self, gate: &dyn GateOp) -> f64 {
164 let qubits = gate.qubits();
165 match qubits.len() {
166 1 => self.single_qubit_error(qubits[0].id() as usize),
167 2 => self.two_qubit_error(qubits[0].id() as usize, qubits[1].id() as usize),
168 _ => 0.1, }
170 }
171}
172
173impl Default for NoiseModel {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct NoiseAwareCostModel {
182 noise_model: NoiseModel,
183 coupling_map: Option<CouplingMap>,
184 pub error_weight: f64,
186 pub time_weight: f64,
188 pub coherence_weight: f64,
190}
191
192impl NoiseAwareCostModel {
193 #[must_use]
195 pub const fn new(noise_model: NoiseModel) -> Self {
196 Self {
197 noise_model,
198 coupling_map: None,
199 error_weight: 1000.0,
200 time_weight: 1.0,
201 coherence_weight: 100.0,
202 }
203 }
204
205 #[must_use]
207 pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
208 self.coupling_map = Some(coupling_map);
209 self
210 }
211
212 pub fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
214 let error_prob = self.noise_model.gate_error_probability(gate);
215 let exec_time = self.noise_model.gate_time(gate.name());
216
217 let mut cost = self
219 .error_weight
220 .mul_add(error_prob, self.time_weight * exec_time);
221
222 if exec_time > 100.0 {
224 let qubits = gate.qubits();
225 for qubit in qubits {
226 let t2 = self.noise_model.t2_time(qubit.id() as usize);
227 let coherence_penalty = exec_time / (t2 * 1000.0); cost += self.coherence_weight * coherence_penalty;
229 }
230 }
231
232 cost
233 }
234
235 #[must_use]
237 pub fn circuit_cost<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
238 let mut total_cost = 0.0;
239 let mut total_time = 0.0;
240
241 for gate in circuit.gates() {
243 total_cost += self.gate_cost(gate.as_ref());
244 total_time += self.noise_model.gate_time(gate.name());
245 }
246
247 if total_time > 0.0 {
249 for i in 0..N {
250 let t2 = self.noise_model.t2_time(i);
251 let coherence_penalty = total_time / (t2 * 1000.0);
252 total_cost += self.coherence_weight * coherence_penalty;
253 }
254 }
255
256 total_cost
257 }
258}
259
260impl CostModel for NoiseAwareCostModel {
261 fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
262 let error_prob = self.noise_model.gate_error_probability(gate);
263 let exec_time = self.noise_model.gate_time(gate.name());
264
265 let mut cost = self
267 .error_weight
268 .mul_add(error_prob, self.time_weight * exec_time);
269
270 if exec_time > 100.0 {
272 let qubits = gate.qubits();
273 for qubit in qubits {
274 let t2 = self.noise_model.t2_time(qubit.id() as usize);
275 let coherence_penalty = exec_time / (t2 * 1000.0); cost += self.coherence_weight * coherence_penalty;
277 }
278 }
279
280 cost
281 }
282
283 fn circuit_cost_from_gates(&self, gates: &[Box<dyn GateOp>]) -> f64 {
284 let mut total_cost = 0.0;
285 let mut total_time = 0.0;
286
287 for gate in gates {
289 total_cost += self.gate_cost(gate.as_ref());
290 total_time += self.noise_model.gate_time(gate.name());
291 }
292
293 total_cost
294 }
295
296 fn weights(&self) -> super::cost_model::CostWeights {
297 super::cost_model::CostWeights {
298 gate_count: 1.0,
299 execution_time: self.time_weight,
300 error_rate: self.error_weight,
301 circuit_depth: 1.0,
302 }
303 }
304
305 fn is_native(&self, gate: &dyn GateOp) -> bool {
306 matches!(
308 gate.name(),
309 "H" | "X" | "Y" | "Z" | "S" | "T" | "CNOT" | "CZ"
310 )
311 }
312}
313
314#[derive(Debug, Clone)]
316pub struct CoherenceOptimization {
317 noise_model: NoiseModel,
318 max_parallel_gates: usize,
319}
320
321impl CoherenceOptimization {
322 #[must_use]
324 pub const fn new(noise_model: NoiseModel) -> Self {
325 Self {
326 noise_model,
327 max_parallel_gates: 10,
328 }
329 }
330
331 fn find_parallel_gates<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<Vec<usize>> {
333 let gates = circuit.gates();
334 let mut parallel_groups = Vec::new();
335 let mut used_qubits = vec![false; N];
336 let mut current_group = Vec::new();
337
338 for (i, gate) in gates.iter().enumerate() {
339 let gate_qubits: Vec<_> = gate.qubits().iter().map(|q| q.id() as usize).collect();
340
341 let conflicts = gate_qubits.iter().any(|&q| used_qubits[q]);
343
344 if conflicts || current_group.len() >= self.max_parallel_gates {
345 if !current_group.is_empty() {
347 parallel_groups.push(current_group);
348 current_group = Vec::new();
349 used_qubits.fill(false);
350 }
351 }
352
353 current_group.push(i);
355 for &q in &gate_qubits {
356 used_qubits[q] = true;
357 }
358 }
359
360 if !current_group.is_empty() {
361 parallel_groups.push(current_group);
362 }
363
364 parallel_groups
365 }
366}
367
368impl OptimizationPass for CoherenceOptimization {
369 fn name(&self) -> &'static str {
370 "CoherenceOptimization"
371 }
372
373 fn apply_to_gates(
374 &self,
375 gates: Vec<Box<dyn GateOp>>,
376 _cost_model: &dyn CostModel,
377 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
378 Ok(gates)
381 }
382
383 fn should_apply(&self) -> bool {
384 true
385 }
386}
387
388#[derive(Debug, Clone)]
390pub struct NoiseAwareMapping {
391 noise_model: NoiseModel,
392 coupling_map: CouplingMap,
393}
394
395impl NoiseAwareMapping {
396 #[must_use]
398 pub const fn new(noise_model: NoiseModel, coupling_map: CouplingMap) -> Self {
399 Self {
400 noise_model,
401 coupling_map,
402 }
403 }
404
405 fn score_assignment(&self, logical_qubits: &[usize], physical_qubits: &[usize]) -> f64 {
407 let mut score = 0.0;
408
409 for (&logical, &physical) in logical_qubits.iter().zip(physical_qubits.iter()) {
411 score += 1.0 / (1.0 + self.noise_model.single_qubit_error(physical));
412 }
413
414 for i in 0..logical_qubits.len() {
416 for j in (i + 1)..logical_qubits.len() {
417 let p1 = physical_qubits[i];
418 let p2 = physical_qubits[j];
419
420 if self.coupling_map.are_connected(p1, p2) {
421 let error = self.noise_model.two_qubit_error(p1, p2);
422 score += 1.0 / (1.0 + error);
423 }
424 }
425 }
426
427 score
428 }
429}
430
431impl OptimizationPass for NoiseAwareMapping {
432 fn name(&self) -> &'static str {
433 "NoiseAwareMapping"
434 }
435
436 fn apply_to_gates(
437 &self,
438 gates: Vec<Box<dyn GateOp>>,
439 _cost_model: &dyn CostModel,
440 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
441 Ok(gates)
444 }
445
446 fn should_apply(&self) -> bool {
447 true
448 }
449}
450
451#[derive(Debug, Clone)]
453pub struct DynamicalDecoupling {
454 noise_model: NoiseModel,
455 pub min_idle_time: f64,
457 pub sequence_type: DecouplingSequence,
459}
460
461#[derive(Debug, Clone)]
463pub enum DecouplingSequence {
464 XY4,
466 CPMG,
468 XY8,
470}
471
472impl DynamicalDecoupling {
473 #[must_use]
475 pub const fn new(noise_model: NoiseModel) -> Self {
476 Self {
477 noise_model,
478 min_idle_time: 1000.0, sequence_type: DecouplingSequence::XY4,
480 }
481 }
482
483 fn analyze_idle_times<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<(usize, f64)> {
485 let mut idle_times = Vec::new();
486
487 for (i, gate) in circuit.gates().iter().enumerate() {
489 let exec_time = self.noise_model.gate_time(gate.name());
490
491 if i + 1 < circuit.num_gates() {
493 idle_times.push((i, 500.0)); }
496 }
497
498 idle_times
499 }
500}
501
502impl OptimizationPass for DynamicalDecoupling {
503 fn name(&self) -> &'static str {
504 "DynamicalDecoupling"
505 }
506
507 fn apply_to_gates(
508 &self,
509 gates: Vec<Box<dyn GateOp>>,
510 _cost_model: &dyn CostModel,
511 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
512 Ok(gates)
515 }
516
517 fn should_apply(&self) -> bool {
518 true
519 }
520}
521
522#[derive(Debug)]
524pub struct NoiseAwareOptimizer {
525 noise_model: NoiseModel,
526 coupling_map: Option<CouplingMap>,
527 cost_model: NoiseAwareCostModel,
528}
529
530impl NoiseAwareOptimizer {
531 #[must_use]
533 pub fn new(noise_model: NoiseModel) -> Self {
534 let cost_model = NoiseAwareCostModel::new(noise_model.clone());
535
536 Self {
537 noise_model,
538 coupling_map: None,
539 cost_model,
540 }
541 }
542
543 #[must_use]
545 pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
546 self.cost_model = self.cost_model.with_coupling_map(coupling_map.clone());
547 self.coupling_map = Some(coupling_map);
548 self
549 }
550
551 #[must_use]
553 pub fn get_passes(&self) -> Vec<Box<dyn OptimizationPass>> {
554 let mut passes: Vec<Box<dyn OptimizationPass>> = Vec::new();
555
556 passes.push(Box::new(CoherenceOptimization::new(
558 self.noise_model.clone(),
559 )));
560
561 if let Some(ref coupling_map) = self.coupling_map {
563 passes.push(Box::new(NoiseAwareMapping::new(
564 self.noise_model.clone(),
565 coupling_map.clone(),
566 )));
567 }
568
569 passes.push(Box::new(DynamicalDecoupling::new(self.noise_model.clone())));
571
572 passes
573 }
574
575 pub fn optimize<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<Circuit<N>> {
593 let mut gates: Vec<Box<dyn GateOp>> =
595 circuit.gates().iter().map(|g| g.clone_gate()).collect();
596
597 let passes = self.get_passes();
599 for pass in &passes {
600 if pass.should_apply() {
601 gates = pass.apply_to_gates(gates, &self.cost_model)?;
602 }
603 }
604
605 Ok(circuit.clone())
611 }
612
613 #[must_use]
615 pub fn estimate_fidelity<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
616 let mut total_error_prob = 0.0;
617
618 for gate in circuit.gates() {
619 let error_prob = self.noise_model.gate_error_probability(gate.as_ref());
620 total_error_prob += error_prob;
621 }
622
623 (1.0 - total_error_prob).max(0.0)
625 }
626
627 #[must_use]
629 pub const fn noise_model(&self) -> &NoiseModel {
630 &self.noise_model
631 }
632
633 #[must_use]
635 pub const fn cost_model(&self) -> &NoiseAwareCostModel {
636 &self.cost_model
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use quantrs2_core::gate::{multi::CNOT, single::Hadamard};
644
645 #[test]
646 fn test_noise_model_creation() {
647 let model = NoiseModel::uniform(4);
648
649 assert_eq!(model.single_qubit_error(0), 1e-3);
650 assert_eq!(model.two_qubit_error(0, 1), 1e-2);
651 assert_eq!(model.t1_time(0), 100.0);
652 assert_eq!(model.gate_time("CNOT"), 200.0);
653 }
654
655 #[test]
656 fn test_ibm_noise_model() {
657 let model = NoiseModel::ibm_like(3);
658
659 assert_eq!(model.single_qubit_error(0), 1e-4);
660 assert_eq!(model.gate_time("H"), 35.0);
661 assert!(model.t1_time(1) > model.t1_time(0));
662 }
663
664 #[test]
665 fn test_noise_aware_cost_model() {
666 let noise_model = NoiseModel::uniform(4);
667 let cost_model = NoiseAwareCostModel::new(noise_model);
668
669 let h_gate = Hadamard { target: QubitId(0) };
670 let cnot_gate = CNOT {
671 control: QubitId(0),
672 target: QubitId(1),
673 };
674
675 let h_cost = cost_model.gate_cost(&h_gate);
676 let cnot_cost = cost_model.gate_cost(&cnot_gate);
677
678 assert!(cnot_cost > h_cost);
680 }
681
682 #[test]
683 fn test_coherence_optimization() {
684 let noise_model = NoiseModel::uniform(4);
685 let optimizer = CoherenceOptimization::new(noise_model.clone());
686
687 let mut circuit = Circuit::<4>::new();
688 circuit
689 .add_gate(Hadamard { target: QubitId(0) })
690 .expect("Failed to add Hadamard gate");
691 circuit
692 .add_gate(CNOT {
693 control: QubitId(0),
694 target: QubitId(1),
695 })
696 .expect("Failed to add CNOT gate");
697
698 let cost_model = NoiseAwareCostModel::new(noise_model);
699 let result = optimizer.apply(&circuit, &cost_model);
700 assert!(result.is_ok());
701 }
702
703 #[test]
704 fn test_noise_aware_optimizer() {
705 let noise_model = NoiseModel::uniform(4);
706 let optimizer = NoiseAwareOptimizer::new(noise_model);
707
708 let mut circuit = Circuit::<4>::new();
709 circuit
710 .add_gate(Hadamard { target: QubitId(0) })
711 .expect("Failed to add Hadamard gate");
712 circuit
713 .add_gate(CNOT {
714 control: QubitId(0),
715 target: QubitId(1),
716 })
717 .expect("Failed to add CNOT gate");
718
719 let optimized = optimizer
720 .optimize(&circuit)
721 .expect("Optimization should succeed");
722 let fidelity = optimizer.estimate_fidelity(&optimized);
723
724 assert!(fidelity > 0.9); assert!(fidelity <= 1.0);
726 }
727}