1use crate::builder::Circuit;
6use crate::optimization::cost_model::CostModel;
7use crate::optimization::gate_properties::{get_gate_properties, CommutationTable};
8use quantrs2_core::decomposition::{decompose_controlled_rotation, GateDecomposable};
9use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
10use quantrs2_core::gate::{
11 multi,
12 single::{self, PauliX, PauliZ, RotationX, RotationY, RotationZ},
13 GateOp,
14};
15use quantrs2_core::qubit::QubitId;
16use std::collections::{HashMap, HashSet};
17use std::f64::consts::PI;
18
19pub trait OptimizationPass: Send + Sync {
21 fn name(&self) -> &str;
23
24 fn apply_to_gates(
26 &self,
27 gates: Vec<Box<dyn GateOp>>,
28 cost_model: &dyn CostModel,
29 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>>;
30
31 fn should_apply(&self) -> bool {
33 true
34 }
35}
36
37pub trait OptimizationPassExt<const N: usize> {
39 fn apply(&self, circuit: &Circuit<N>, cost_model: &dyn CostModel)
40 -> QuantRS2Result<Circuit<N>>;
41 fn should_apply_to_circuit(&self, circuit: &Circuit<N>) -> bool;
42}
43
44impl<T: OptimizationPass + ?Sized, const N: usize> OptimizationPassExt<N> for T {
45 fn apply(
46 &self,
47 circuit: &Circuit<N>,
48 cost_model: &dyn CostModel,
49 ) -> QuantRS2Result<Circuit<N>> {
50 Ok(circuit.clone())
52 }
53
54 fn should_apply_to_circuit(&self, _circuit: &Circuit<N>) -> bool {
55 self.should_apply()
56 }
57}
58
59pub struct GateCancellation {
61 aggressive: bool,
62}
63
64impl GateCancellation {
65 #[must_use]
66 pub const fn new(aggressive: bool) -> Self {
67 Self { aggressive }
68 }
69}
70
71impl OptimizationPass for GateCancellation {
72 fn name(&self) -> &'static str {
73 "Gate Cancellation"
74 }
75
76 fn apply_to_gates(
77 &self,
78 gates: Vec<Box<dyn GateOp>>,
79 _cost_model: &dyn CostModel,
80 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
81 let mut optimized = Vec::new();
82 let mut i = 0;
83
84 while i < gates.len() {
85 if i + 1 < gates.len() {
86 let gate1 = &gates[i];
87 let gate2 = &gates[i + 1];
88
89 if gate1.qubits() == gate2.qubits() && gate1.name() == gate2.name() {
91 match gate1.name() {
93 "H" | "X" | "Y" | "Z" => {
94 i += 2;
96 continue;
97 }
98 "RX" | "RY" | "RZ" => {
99 if let (Some(rx1), Some(rx2)) = (
101 gate1.as_any().downcast_ref::<single::RotationX>(),
102 gate2.as_any().downcast_ref::<single::RotationX>(),
103 ) {
104 let combined_angle = rx1.theta + rx2.theta;
105 if (combined_angle % (2.0 * PI)).abs() < 1e-10 {
107 i += 2;
108 continue;
109 }
110 } else if let (Some(ry1), Some(ry2)) = (
111 gate1.as_any().downcast_ref::<single::RotationY>(),
112 gate2.as_any().downcast_ref::<single::RotationY>(),
113 ) {
114 let combined_angle = ry1.theta + ry2.theta;
115 if (combined_angle % (2.0 * PI)).abs() < 1e-10 {
116 i += 2;
117 continue;
118 }
119 } else if let (Some(rz1), Some(rz2)) = (
120 gate1.as_any().downcast_ref::<single::RotationZ>(),
121 gate2.as_any().downcast_ref::<single::RotationZ>(),
122 ) {
123 let combined_angle = rz1.theta + rz2.theta;
124 if (combined_angle % (2.0 * PI)).abs() < 1e-10 {
125 i += 2;
126 continue;
127 }
128 }
129 }
130 "CNOT" => {
131 if let (Some(cnot1), Some(cnot2)) = (
133 gate1.as_any().downcast_ref::<multi::CNOT>(),
134 gate2.as_any().downcast_ref::<multi::CNOT>(),
135 ) {
136 if cnot1.control == cnot2.control && cnot1.target == cnot2.target {
137 i += 2;
138 continue;
139 }
140 }
141 }
142 _ => {}
143 }
144 }
145
146 if self.aggressive && i + 2 < gates.len() {
148 let gate3 = &gates[i + 2];
150 if gate1.qubits() == gate3.qubits()
151 && gate1.name() == gate3.name()
152 && i + 3 < gates.len()
153 {
154 let gate4 = &gates[i + 3];
155 if gate2.qubits() == gate4.qubits()
156 && gate2.name() == gate4.name()
157 && gate1.qubits() == gate2.qubits()
158 {
159 match (gate1.name(), gate2.name()) {
161 ("X", "Y") | ("Y", "X") | ("Z", "H") | ("H", "Z") => {
162 }
165 _ => {}
166 }
167 }
168 }
169 }
170 }
171
172 optimized.push(gates[i].clone());
174 i += 1;
175 }
176
177 Ok(optimized)
178 }
179}
180
181pub struct GateCommutation {
183 max_lookahead: usize,
184 commutation_table: CommutationTable,
185}
186
187impl GateCommutation {
188 #[must_use]
189 pub fn new(max_lookahead: usize) -> Self {
190 Self {
191 max_lookahead,
192 commutation_table: CommutationTable::new(),
193 }
194 }
195}
196
197impl GateCommutation {
198 fn gates_commute(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
200 if self.commutation_table.commutes(gate1.name(), gate2.name()) {
202 return true;
203 }
204
205 match (gate1.name(), gate2.name()) {
207 ("X", "X") | ("Y", "Y") | ("Z", "Z") => true,
209 ("I", _) | (_, "I") => true,
210
211 ("S" | "T", "Z") | ("Z", "S" | "T") => true,
213
214 ("RX", "RX") | ("RY", "RY") | ("RZ", "RZ") => true,
216
217 ("RZ", "Z" | "S" | "T") | ("Z" | "S" | "T", "RZ") => true,
219
220 _ => false,
221 }
222 }
223
224 fn would_benefit_from_swap(&self, gates: &[Box<dyn GateOp>], i: usize) -> bool {
226 if i + 2 >= gates.len() {
227 return false;
228 }
229
230 let gate1 = &gates[i];
231 let gate2 = &gates[i + 1];
232 let gate3 = &gates[i + 2];
233
234 if gate1.name() == gate3.name() && gate1.qubits() == gate3.qubits() {
236 match gate3.name() {
238 "H" | "X" | "Y" | "Z" => return true,
239 _ => {}
240 }
241 }
242
243 if gate2.name() == gate3.name() && gate2.qubits() == gate3.qubits() {
245 match gate2.name() {
246 "RX" | "RY" | "RZ" => return true,
247 _ => {}
248 }
249 }
250
251 false
252 }
253}
254
255impl OptimizationPass for GateCommutation {
256 fn name(&self) -> &'static str {
257 "Gate Commutation"
258 }
259
260 fn apply_to_gates(
261 &self,
262 gates: Vec<Box<dyn GateOp>>,
263 _cost_model: &dyn CostModel,
264 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
265 if gates.len() < 2 {
266 return Ok(gates);
267 }
268
269 let mut optimized = gates;
270 let mut changed = true;
271
272 while changed {
274 changed = false;
275 let mut i = 0;
276
277 while i < optimized.len().saturating_sub(1) {
278 let can_swap = {
279 let gate1 = &optimized[i];
280 let gate2 = &optimized[i + 1];
281
282 let qubits1: HashSet<_> = gate1.qubits().into_iter().collect();
284 let qubits2: HashSet<_> = gate2.qubits().into_iter().collect();
285
286 if qubits1.is_disjoint(&qubits2) {
287 self.would_benefit_from_swap(&optimized, i)
290 } else if qubits1 == qubits2 {
291 self.gates_commute(gate1.as_ref(), gate2.as_ref())
293 } else {
294 false
296 }
297 };
298
299 if can_swap {
300 optimized.swap(i, i + 1);
301 changed = true;
302 i = i.saturating_sub(1);
304 } else {
305 i += 1;
306 }
307
308 if i >= self.max_lookahead {
310 break;
311 }
312 }
313 }
314
315 Ok(optimized)
316 }
317}
318
319pub struct GateMerging {
321 merge_rotations: bool,
322 merge_threshold: f64,
323}
324
325impl GateMerging {
326 #[must_use]
327 pub const fn new(merge_rotations: bool, merge_threshold: f64) -> Self {
328 Self {
329 merge_rotations,
330 merge_threshold,
331 }
332 }
333}
334
335impl OptimizationPass for GateMerging {
336 fn name(&self) -> &'static str {
337 "Gate Merging"
338 }
339
340 fn apply_to_gates(
341 &self,
342 gates: Vec<Box<dyn GateOp>>,
343 _cost_model: &dyn CostModel,
344 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
345 let mut optimized = Vec::new();
346 let mut i = 0;
347
348 while i < gates.len() {
349 if i + 1 < gates.len() && self.merge_rotations {
350 let gate1 = &gates[i];
351 let gate2 = &gates[i + 1];
352
353 if gate1.qubits() == gate2.qubits() {
355 let merged = match (gate1.name(), gate2.name()) {
356 ("RX", "RX") | ("RY", "RY") | ("RZ", "RZ") => {
358 None
360 }
361 ("RZ" | "RY", "RX") | ("RX" | "RY", "RZ") | ("RX" | "RZ", "RY")
363 if self.merge_threshold > 0.0 =>
364 {
365 None
368 }
369 ("S" | "T", "RZ") | ("RZ", "S" | "T") => {
371 None
374 }
375 _ => None,
376 };
377
378 if let Some(merged_gate) = merged {
379 optimized.push(merged_gate);
380 i += 2;
381 continue;
382 }
383 }
384 }
385
386 if i + 1 < gates.len() {
388 let gate1 = &gates[i];
389 let gate2 = &gates[i + 1];
390
391 if i + 2 < gates.len() {
393 let gate3 = &gates[i + 2];
394 if gate1.name() == "H"
395 && gate3.name() == "H"
396 && gate1.qubits() == gate2.qubits()
397 && gate2.qubits() == gate3.qubits()
398 {
399 match gate2.name() {
400 "Z" => {
401 optimized.push(Box::new(single::PauliX {
403 target: gate1.qubits()[0],
404 })
405 as Box<dyn GateOp>);
406 i += 3;
407 continue;
408 }
409 "X" => {
410 optimized.push(Box::new(single::PauliZ {
412 target: gate1.qubits()[0],
413 })
414 as Box<dyn GateOp>);
415 i += 3;
416 continue;
417 }
418 _ => {}
419 }
420 }
421 }
422 }
423
424 optimized.push(gates[i].clone());
426 i += 1;
427 }
428
429 Ok(optimized)
430 }
431}
432
433pub struct RotationMerging {
435 tolerance: f64,
436}
437
438impl RotationMerging {
439 #[must_use]
440 pub const fn new(tolerance: f64) -> Self {
441 Self { tolerance }
442 }
443
444 fn is_zero_rotation(&self, angle: f64) -> bool {
446 let normalized = angle % (2.0 * PI);
447 normalized.abs() < self.tolerance || 2.0f64.mul_add(-PI, normalized).abs() < self.tolerance
448 }
449
450 fn merge_angles(&self, angle1: f64, angle2: f64) -> f64 {
452 let merged = angle1 + angle2;
453 let normalized = merged % (2.0 * PI);
454 if normalized > PI {
455 2.0f64.mul_add(-PI, normalized)
456 } else if normalized < -PI {
457 2.0f64.mul_add(PI, normalized)
458 } else {
459 normalized
460 }
461 }
462}
463
464impl OptimizationPass for RotationMerging {
465 fn name(&self) -> &'static str {
466 "Rotation Merging"
467 }
468
469 fn apply_to_gates(
470 &self,
471 gates: Vec<Box<dyn GateOp>>,
472 _cost_model: &dyn CostModel,
473 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
474 let mut optimized = Vec::new();
475 let mut i = 0;
476
477 while i < gates.len() {
478 if i + 1 < gates.len() {
479 let gate1 = &gates[i];
480 let gate2 = &gates[i + 1];
481
482 if gate1.qubits() == gate2.qubits() && gate1.name() == gate2.name() {
484 match gate1.name() {
485 "RX" => {
486 if let (Some(rx1), Some(rx2)) = (
487 gate1.as_any().downcast_ref::<single::RotationX>(),
488 gate2.as_any().downcast_ref::<single::RotationX>(),
489 ) {
490 let merged_angle = self.merge_angles(rx1.theta, rx2.theta);
491 if self.is_zero_rotation(merged_angle) {
492 i += 2;
494 continue;
495 }
496 optimized.push(Box::new(single::RotationX {
498 target: rx1.target,
499 theta: merged_angle,
500 })
501 as Box<dyn GateOp>);
502 i += 2;
503 continue;
504 }
505 }
506 "RY" => {
507 if let (Some(ry1), Some(ry2)) = (
508 gate1.as_any().downcast_ref::<single::RotationY>(),
509 gate2.as_any().downcast_ref::<single::RotationY>(),
510 ) {
511 let merged_angle = self.merge_angles(ry1.theta, ry2.theta);
512 if self.is_zero_rotation(merged_angle) {
513 i += 2;
514 continue;
515 }
516 optimized.push(Box::new(single::RotationY {
517 target: ry1.target,
518 theta: merged_angle,
519 })
520 as Box<dyn GateOp>);
521 i += 2;
522 continue;
523 }
524 }
525 "RZ" => {
526 if let (Some(rz1), Some(rz2)) = (
527 gate1.as_any().downcast_ref::<single::RotationZ>(),
528 gate2.as_any().downcast_ref::<single::RotationZ>(),
529 ) {
530 let merged_angle = self.merge_angles(rz1.theta, rz2.theta);
531 if self.is_zero_rotation(merged_angle) {
532 i += 2;
533 continue;
534 }
535 optimized.push(Box::new(single::RotationZ {
536 target: rz1.target,
537 theta: merged_angle,
538 })
539 as Box<dyn GateOp>);
540 i += 2;
541 continue;
542 }
543 }
544 _ => {}
545 }
546 }
547 }
548
549 optimized.push(gates[i].clone());
551 i += 1;
552 }
553
554 Ok(optimized)
555 }
556}
557
558pub struct DecompositionOptimization {
560 target_gate_set: HashSet<String>,
561 prefer_native: bool,
562}
563
564impl DecompositionOptimization {
565 #[must_use]
566 pub const fn new(target_gate_set: HashSet<String>, prefer_native: bool) -> Self {
567 Self {
568 target_gate_set,
569 prefer_native,
570 }
571 }
572
573 #[must_use]
574 pub fn for_hardware(hardware: &str) -> Self {
575 let target_gate_set = match hardware {
576 "ibm" => vec!["X", "Y", "Z", "H", "S", "T", "RZ", "CNOT", "CZ"]
577 .into_iter()
578 .map(std::string::ToString::to_string)
579 .collect(),
580 "google" => vec!["X", "Y", "Z", "H", "RZ", "CZ", "SQRT_X"]
581 .into_iter()
582 .map(std::string::ToString::to_string)
583 .collect(),
584 _ => vec!["X", "Y", "Z", "H", "S", "T", "RZ", "RX", "RY", "CNOT"]
585 .into_iter()
586 .map(std::string::ToString::to_string)
587 .collect(),
588 };
589
590 Self {
591 target_gate_set,
592 prefer_native: true,
593 }
594 }
595}
596
597impl OptimizationPass for DecompositionOptimization {
598 fn name(&self) -> &'static str {
599 "Decomposition Optimization"
600 }
601
602 fn apply_to_gates(
603 &self,
604 gates: Vec<Box<dyn GateOp>>,
605 cost_model: &dyn CostModel,
606 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
607 let mut optimized_gates = Vec::with_capacity(gates.len() * 2);
608
609 for gate in gates {
610 let gate_name = gate.name();
611 let gate_qubits = gate.qubits();
612
613 if self.should_decompose(&gate, cost_model) {
615 match gate_name {
617 "Toffoli" => {
618 if gate_qubits.len() == 3 {
619 self.decompose_toffoli(&gate_qubits, &mut optimized_gates)?;
621 } else {
622 optimized_gates.push(gate);
623 }
624 }
625 "Fredkin" | "CSWAP" => {
626 if gate_qubits.len() == 3 {
627 self.decompose_fredkin(&gate_qubits, &mut optimized_gates)?;
629 } else {
630 optimized_gates.push(gate);
631 }
632 }
633 "SWAP" => {
634 if self.target_gate_set.contains("CNOT") && gate_qubits.len() == 2 {
635 self.decompose_swap(&gate_qubits, &mut optimized_gates)?;
637 } else {
638 optimized_gates.push(gate);
639 }
640 }
641 "CRX" | "CRY" | "CRZ" => {
642 if !self.target_gate_set.contains(gate_name) && gate_qubits.len() == 2 {
644 self.decompose_controlled_rotation(&gate, &mut optimized_gates)?;
645 } else {
646 optimized_gates.push(gate);
647 }
648 }
649 _ => {
650 optimized_gates.push(gate);
652 }
653 }
654 } else {
655 optimized_gates.push(gate);
656 }
657 }
658
659 Ok(optimized_gates)
660 }
661}
662
663impl DecompositionOptimization {
664 fn should_decompose(&self, gate: &Box<dyn GateOp>, _cost_model: &dyn CostModel) -> bool {
667 let gate_name = gate.name();
668
669 if self.target_gate_set.contains(gate_name) {
671 false
672 } else {
673 matches!(
675 gate_name,
676 "Toffoli" | "Fredkin" | "CSWAP" | "SWAP" | "CRX" | "CRY" | "CRZ"
677 )
678 }
679 }
680
681 fn decompose_toffoli(
682 &self,
683 qubits: &[QubitId],
684 gates: &mut Vec<Box<dyn GateOp>>,
685 ) -> QuantRS2Result<()> {
686 if qubits.len() != 3 {
687 return Err(quantrs2_core::error::QuantRS2Error::InvalidInput(
688 "Toffoli gate requires exactly 3 qubits".to_string(),
689 ));
690 }
691
692 let c1 = qubits[0];
693 let c2 = qubits[1];
694 let target = qubits[2];
695
696 use quantrs2_core::gate::{
698 multi::CNOT,
699 single::{Hadamard, TDagger, T},
700 };
701
702 gates.push(Box::new(Hadamard { target }));
703 gates.push(Box::new(CNOT {
704 control: c2,
705 target,
706 }));
707 gates.push(Box::new(TDagger { target }));
708 gates.push(Box::new(CNOT {
709 control: c1,
710 target,
711 }));
712 gates.push(Box::new(T { target }));
713 gates.push(Box::new(CNOT {
714 control: c2,
715 target,
716 }));
717 gates.push(Box::new(TDagger { target }));
718 gates.push(Box::new(CNOT {
719 control: c1,
720 target,
721 }));
722 gates.push(Box::new(T { target: c2 }));
723 gates.push(Box::new(T { target }));
724 gates.push(Box::new(CNOT {
725 control: c1,
726 target: c2,
727 }));
728 gates.push(Box::new(Hadamard { target }));
729 gates.push(Box::new(T { target: c1 }));
730 gates.push(Box::new(TDagger { target: c2 }));
731 gates.push(Box::new(CNOT {
732 control: c1,
733 target: c2,
734 }));
735
736 Ok(())
737 }
738
739 fn decompose_fredkin(
740 &self,
741 qubits: &[QubitId],
742 gates: &mut Vec<Box<dyn GateOp>>,
743 ) -> QuantRS2Result<()> {
744 if qubits.len() != 3 {
745 return Err(quantrs2_core::error::QuantRS2Error::InvalidInput(
746 "Fredkin gate requires exactly 3 qubits".to_string(),
747 ));
748 }
749
750 let control = qubits[0];
751 let target1 = qubits[1];
752 let target2 = qubits[2];
753
754 use quantrs2_core::gate::multi::CNOT;
756
757 gates.push(Box::new(CNOT {
758 control: target2,
759 target: target1,
760 }));
761 gates.push(Box::new(CNOT {
762 control,
763 target: target1,
764 }));
765 gates.push(Box::new(CNOT {
766 control: target1,
767 target: target2,
768 }));
769 gates.push(Box::new(CNOT {
770 control,
771 target: target1,
772 }));
773 gates.push(Box::new(CNOT {
774 control: target2,
775 target: target1,
776 }));
777
778 Ok(())
779 }
780
781 fn decompose_swap(
782 &self,
783 qubits: &[QubitId],
784 gates: &mut Vec<Box<dyn GateOp>>,
785 ) -> QuantRS2Result<()> {
786 if qubits.len() != 2 {
787 return Err(quantrs2_core::error::QuantRS2Error::InvalidInput(
788 "SWAP gate requires exactly 2 qubits".to_string(),
789 ));
790 }
791
792 let q1 = qubits[0];
793 let q2 = qubits[1];
794
795 use quantrs2_core::gate::multi::CNOT;
797
798 gates.push(Box::new(CNOT {
799 control: q1,
800 target: q2,
801 }));
802 gates.push(Box::new(CNOT {
803 control: q2,
804 target: q1,
805 }));
806 gates.push(Box::new(CNOT {
807 control: q1,
808 target: q2,
809 }));
810
811 Ok(())
812 }
813
814 fn decompose_controlled_rotation(
815 &self,
816 gate: &Box<dyn GateOp>,
817 gates: &mut Vec<Box<dyn GateOp>>,
818 ) -> QuantRS2Result<()> {
819 let qubits = gate.qubits();
820 if qubits.len() != 2 {
821 return Err(quantrs2_core::error::QuantRS2Error::InvalidInput(
822 "Controlled rotation requires exactly 2 qubits".to_string(),
823 ));
824 }
825
826 let control = qubits[0];
827 let target = qubits[1];
828
829 use quantrs2_core::gate::{
832 multi::CNOT,
833 single::{RotationX, RotationY, RotationZ},
834 };
835
836 match gate.name() {
837 "CRX" => {
838 gates.push(Box::new(RotationX {
839 target,
840 theta: std::f64::consts::PI / 4.0,
841 }));
842 gates.push(Box::new(CNOT { control, target }));
843 gates.push(Box::new(RotationX {
844 target,
845 theta: -std::f64::consts::PI / 4.0,
846 }));
847 gates.push(Box::new(CNOT { control, target }));
848 }
849 "CRY" => {
850 gates.push(Box::new(RotationY {
851 target,
852 theta: std::f64::consts::PI / 4.0,
853 }));
854 gates.push(Box::new(CNOT { control, target }));
855 gates.push(Box::new(RotationY {
856 target,
857 theta: -std::f64::consts::PI / 4.0,
858 }));
859 gates.push(Box::new(CNOT { control, target }));
860 }
861 "CRZ" => {
862 gates.push(Box::new(RotationZ {
863 target,
864 theta: std::f64::consts::PI / 4.0,
865 }));
866 gates.push(Box::new(CNOT { control, target }));
867 gates.push(Box::new(RotationZ {
868 target,
869 theta: -std::f64::consts::PI / 4.0,
870 }));
871 gates.push(Box::new(CNOT { control, target }));
872 }
873 _ => {
874 return Err(quantrs2_core::error::QuantRS2Error::UnsupportedOperation(
875 format!("Unknown controlled rotation gate: {}", gate.name()),
876 ));
877 }
878 }
879
880 Ok(())
881 }
882}
883
884pub struct CostBasedOptimization {
886 optimization_target: CostTarget,
887 max_iterations: usize,
888}
889
890#[derive(Debug, Clone, Copy)]
891pub enum CostTarget {
892 GateCount,
893 CircuitDepth,
894 TotalError,
895 ExecutionTime,
896 Balanced,
897}
898
899impl CostBasedOptimization {
900 #[must_use]
901 pub const fn new(target: CostTarget, max_iterations: usize) -> Self {
902 Self {
903 optimization_target: target,
904 max_iterations,
905 }
906 }
907}
908
909impl OptimizationPass for CostBasedOptimization {
910 fn name(&self) -> &'static str {
911 "Cost-Based Optimization"
912 }
913
914 fn apply_to_gates(
915 &self,
916 gates: Vec<Box<dyn GateOp>>,
917 cost_model: &dyn CostModel,
918 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
919 let mut best_gates = gates.clone();
920 let mut best_cost = self.calculate_cost(&best_gates, cost_model);
921
922 for iteration in 0..self.max_iterations {
923 let candidate_gates = self.generate_candidate_solution(&best_gates, iteration)?;
924 let candidate_cost = self.calculate_cost(&candidate_gates, cost_model);
925
926 if candidate_cost < best_cost {
927 best_gates = candidate_gates;
928 best_cost = candidate_cost;
929 }
930 }
931
932 Ok(best_gates)
933 }
934}
935
936impl CostBasedOptimization {
937 fn calculate_cost(&self, gates: &[Box<dyn GateOp>], cost_model: &dyn CostModel) -> f64 {
940 match self.optimization_target {
941 CostTarget::GateCount => gates.len() as f64,
942 CostTarget::CircuitDepth => self.calculate_depth(gates) as f64,
943 CostTarget::TotalError => self.calculate_total_error(gates),
944 CostTarget::ExecutionTime => self.calculate_execution_time(gates),
945 CostTarget::Balanced => {
946 let gate_count = gates.len() as f64;
948 let depth = self.calculate_depth(gates) as f64;
949 let error = self.calculate_total_error(gates);
950 let time = self.calculate_execution_time(gates);
951
952 (0.2 * error).mul_add(1000.0, 0.3f64.mul_add(gate_count, 0.3 * depth))
953 + 0.2 * time / 1000.0
954 }
955 }
956 }
957
958 fn generate_candidate_solution(
959 &self,
960 gates: &[Box<dyn GateOp>],
961 iteration: usize,
962 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
963 let mut candidate = gates.to_vec();
964
965 match self.optimization_target {
967 CostTarget::GateCount => {
968 self.cancel_inverse_gates(&mut candidate);
970 }
971 CostTarget::CircuitDepth => {
972 self.parallelize_gates(&mut candidate);
974 }
975 CostTarget::TotalError => {
976 self.reduce_error_gates(&candidate)?;
978 }
979 CostTarget::ExecutionTime => {
980 self.optimize_for_speed(&candidate)?;
982 }
983 CostTarget::Balanced => {
984 match iteration % 4 {
986 0 => self.cancel_inverse_gates(&mut candidate),
987 1 => self.parallelize_gates(&mut candidate),
988 2 => self.reduce_error_gates(&candidate)?,
989 3 => self.optimize_for_speed(&candidate)?,
990 _ => unreachable!(),
991 }
992 }
993 }
994
995 Ok(candidate)
996 }
997
998 fn calculate_depth(&self, gates: &[Box<dyn GateOp>]) -> usize {
999 let mut qubit_depths = std::collections::HashMap::new();
1001 let mut max_depth = 0;
1002
1003 for gate in gates {
1004 let gate_qubits = gate.qubits();
1005 let gate_start_depth = gate_qubits
1006 .iter()
1007 .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
1008 .max()
1009 .unwrap_or(0);
1010
1011 let gate_end_depth = gate_start_depth + 1;
1012
1013 for qubit in gate_qubits {
1014 qubit_depths.insert(qubit.id(), gate_end_depth);
1015 }
1016
1017 max_depth = max_depth.max(gate_end_depth);
1018 }
1019
1020 max_depth
1021 }
1022
1023 fn calculate_total_error(&self, gates: &[Box<dyn GateOp>]) -> f64 {
1024 gates
1025 .iter()
1026 .map(|gate| self.estimate_gate_error(gate.name()))
1027 .sum()
1028 }
1029
1030 fn calculate_execution_time(&self, gates: &[Box<dyn GateOp>]) -> f64 {
1031 gates
1032 .iter()
1033 .map(|gate| self.estimate_gate_time(gate.name()))
1034 .sum()
1035 }
1036
1037 fn estimate_gate_error(&self, gate_name: &str) -> f64 {
1038 match gate_name {
1039 "H" | "X" | "Y" | "Z" | "S" | "T" => 0.0001,
1040 "RX" | "RY" | "RZ" => 0.0005,
1041 "CNOT" | "CX" | "CZ" | "CY" => 0.01,
1042 "SWAP" | "CRX" | "CRY" | "CRZ" => 0.015,
1043 "Toffoli" | "Fredkin" => 0.05,
1044 _ => 0.01,
1045 }
1046 }
1047
1048 fn estimate_gate_time(&self, gate_name: &str) -> f64 {
1049 match gate_name {
1050 "H" | "X" | "Y" | "Z" | "S" | "T" | "RX" | "RY" | "RZ" => 50.0,
1051 "CNOT" | "CX" | "CZ" | "CY" | "SWAP" | "CRX" | "CRY" | "CRZ" => 200.0,
1052 "Toffoli" | "Fredkin" => 500.0,
1053 _ => 100.0,
1054 }
1055 }
1056
1057 fn cancel_inverse_gates(&self, gates: &mut Vec<Box<dyn GateOp>>) {
1058 let mut i = 0;
1059 while i + 1 < gates.len() {
1060 if self.are_inverse_gates(&gates[i], &gates[i + 1]) {
1061 gates.remove(i + 1);
1062 gates.remove(i);
1063 i = i.saturating_sub(1);
1064 } else {
1065 i += 1;
1066 }
1067 }
1068 }
1069
1070 fn are_inverse_gates(&self, gate1: &Box<dyn GateOp>, gate2: &Box<dyn GateOp>) -> bool {
1071 if gate1.qubits() != gate2.qubits() {
1072 return false;
1073 }
1074
1075 match (gate1.name(), gate2.name()) {
1076 ("H", "H") | ("X", "X") | ("Y", "Y") | ("Z", "Z") => true,
1077 ("S", "SDG") | ("SDG", "S") => true,
1078 ("T", "TDG") | ("TDG", "T") => true,
1079 ("CNOT", "CNOT") | ("CX", "CX") => true,
1080 _ => false,
1081 }
1082 }
1083
1084 fn parallelize_gates(&self, _gates: &mut Vec<Box<dyn GateOp>>) {
1085 }
1088
1089 fn reduce_error_gates(&self, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<()> {
1090 for i in 0..gates.len() {
1092 if gates[i].name() == "Toffoli" {
1093 } else {
1096 }
1098 }
1099 Ok(())
1100 }
1101
1102 fn optimize_for_speed(&self, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<()> {
1103 for i in 0..gates.len() {
1105 if gates[i].name() == "Toffoli" {
1106 } else {
1108 }
1110 }
1111 Ok(())
1112 }
1113}
1114
1115pub struct TwoQubitOptimization {
1117 use_kak_decomposition: bool,
1118 optimize_cnots: bool,
1119}
1120
1121impl TwoQubitOptimization {
1122 #[must_use]
1123 pub const fn new(use_kak_decomposition: bool, optimize_cnots: bool) -> Self {
1124 Self {
1125 use_kak_decomposition,
1126 optimize_cnots,
1127 }
1128 }
1129}
1130
1131impl OptimizationPass for TwoQubitOptimization {
1132 fn name(&self) -> &'static str {
1133 "Two-Qubit Optimization"
1134 }
1135
1136 fn apply_to_gates(
1137 &self,
1138 gates: Vec<Box<dyn GateOp>>,
1139 _cost_model: &dyn CostModel,
1140 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1141 Ok(gates)
1143 }
1144}
1145
1146pub struct PeepholeOptimization {
1148 window_size: usize,
1149 patterns: Vec<PeepholePattern>,
1150}
1151
1152#[derive(Clone)]
1153pub struct PeepholePattern {
1154 name: String,
1155 window_size: usize,
1156 matcher: fn(&[Box<dyn GateOp>]) -> Option<Vec<Box<dyn GateOp>>>,
1157}
1158
1159impl PeepholeOptimization {
1160 #[must_use]
1161 pub fn new(window_size: usize) -> Self {
1162 let patterns = vec![
1163 PeepholePattern {
1165 name: "X-Y-X to -Y".to_string(),
1166 window_size: 3,
1167 matcher: |gates| {
1168 if gates.len() >= 3 {
1169 let g0 = &gates[0];
1170 let g1 = &gates[1];
1171 let g2 = &gates[2];
1172
1173 if g0.name() == "X"
1174 && g2.name() == "X"
1175 && g1.name() == "Y"
1176 && g0.qubits() == g1.qubits()
1177 && g1.qubits() == g2.qubits()
1178 {
1179 return Some(vec![g1.clone()]);
1181 }
1182 }
1183 None
1184 },
1185 },
1186 PeepholePattern {
1188 name: "H-S-H simplification".to_string(),
1189 window_size: 3,
1190 matcher: |gates| {
1191 if gates.len() >= 3 {
1192 let g0 = &gates[0];
1193 let g1 = &gates[1];
1194 let g2 = &gates[2];
1195
1196 if g0.name() == "H"
1197 && g2.name() == "H"
1198 && g1.name() == "S"
1199 && g0.qubits() == g1.qubits()
1200 && g1.qubits() == g2.qubits()
1201 {
1202 let target = g0.qubits()[0];
1203 return Some(vec![
1204 Box::new(single::PauliX { target }) as Box<dyn GateOp>,
1205 Box::new(single::RotationZ {
1206 target,
1207 theta: PI / 2.0,
1208 }) as Box<dyn GateOp>,
1209 Box::new(single::PauliX { target }) as Box<dyn GateOp>,
1210 ]);
1211 }
1212 }
1213 None
1214 },
1215 },
1216 PeepholePattern {
1218 name: "Euler angle optimization".to_string(),
1219 window_size: 3,
1220 matcher: |gates| {
1221 if gates.len() >= 3 {
1222 let g0 = &gates[0];
1223 let g1 = &gates[1];
1224 let g2 = &gates[2];
1225
1226 if g0.name() == "RZ"
1227 && g1.name() == "RX"
1228 && g2.name() == "RZ"
1229 && g0.qubits() == g1.qubits()
1230 && g1.qubits() == g2.qubits()
1231 {
1232 if let (Some(rz1), Some(rx), Some(rz2)) = (
1234 g0.as_any().downcast_ref::<single::RotationZ>(),
1235 g1.as_any().downcast_ref::<single::RotationX>(),
1236 g2.as_any().downcast_ref::<single::RotationZ>(),
1237 ) {
1238 if rx.theta.abs() < 1e-10 {
1240 let combined_angle = rz1.theta + rz2.theta;
1242 if combined_angle.abs() < 1e-10 {
1243 return Some(vec![]); }
1245 return Some(vec![Box::new(single::RotationZ {
1246 target: rz1.target,
1247 theta: combined_angle,
1248 })
1249 as Box<dyn GateOp>]);
1250 }
1251 }
1252 }
1253 }
1254 None
1255 },
1256 },
1257 PeepholePattern {
1259 name: "Phase gadget optimization".to_string(),
1260 window_size: 3,
1261 matcher: |gates| {
1262 if gates.len() >= 3 {
1263 let g0 = &gates[0];
1264 let g1 = &gates[1];
1265 let g2 = &gates[2];
1266
1267 if g0.name() == "CNOT" && g2.name() == "CNOT" && g1.name() == "RZ" {
1268 if let (Some(cnot1), Some(rz), Some(cnot2)) = (
1269 g0.as_any().downcast_ref::<multi::CNOT>(),
1270 g1.as_any().downcast_ref::<single::RotationZ>(),
1271 g2.as_any().downcast_ref::<multi::CNOT>(),
1272 ) {
1273 if cnot1.control == cnot2.control
1275 && cnot1.target == cnot2.target
1276 && rz.target == cnot1.target
1277 {
1278 return None;
1281 }
1282 }
1283 }
1284 }
1285 None
1286 },
1287 },
1288 PeepholePattern {
1290 name: "Hadamard ladder".to_string(),
1291 window_size: 4,
1292 matcher: |gates| {
1293 if gates.len() >= 4 {
1294 if gates[0].name() == "H"
1296 && gates[1].name() == "CNOT"
1297 && gates[2].name() == "H"
1298 && gates[3].name() == "CNOT"
1299 {
1300 let h1_target = gates[0].qubits()[0];
1302 let h2_target = gates[2].qubits()[0];
1303
1304 if let (Some(cnot1), Some(cnot2)) = (
1305 gates[1].as_any().downcast_ref::<multi::CNOT>(),
1306 gates[3].as_any().downcast_ref::<multi::CNOT>(),
1307 ) {
1308 if h1_target == cnot1.control
1309 && h2_target == cnot2.control
1310 && cnot1.target == cnot2.target
1311 {
1312 return None; }
1315 }
1316 }
1317 }
1318 None
1319 },
1320 },
1321 ];
1322
1323 Self {
1324 window_size,
1325 patterns,
1326 }
1327 }
1328
1329 fn apply_patterns(&self, window: &[Box<dyn GateOp>]) -> Option<Vec<Box<dyn GateOp>>> {
1331 for pattern in &self.patterns {
1332 if window.len() >= pattern.window_size {
1333 if let Some(replacement) = (pattern.matcher)(window) {
1334 return Some(replacement);
1335 }
1336 }
1337 }
1338 None
1339 }
1340}
1341
1342impl OptimizationPass for PeepholeOptimization {
1343 fn name(&self) -> &'static str {
1344 "Peephole Optimization"
1345 }
1346
1347 fn apply_to_gates(
1348 &self,
1349 gates: Vec<Box<dyn GateOp>>,
1350 _cost_model: &dyn CostModel,
1351 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1352 let mut optimized = Vec::new();
1353 let mut i = 0;
1354
1355 while i < gates.len() {
1356 let mut matched = false;
1358
1359 for window_size in (2..=self.window_size).rev() {
1361 if i + window_size <= gates.len() {
1362 let window = &gates[i..i + window_size];
1363
1364 if let Some(replacement) = self.apply_patterns(window) {
1365 optimized.extend(replacement);
1367 i += window_size;
1368 matched = true;
1369 break;
1370 }
1371 }
1372 }
1373
1374 if !matched {
1376 optimized.push(gates[i].clone());
1377 i += 1;
1378 }
1379 }
1380
1381 Ok(optimized)
1382 }
1383}
1384
1385pub struct TemplateMatching {
1387 templates: Vec<CircuitTemplate>,
1388}
1389
1390#[derive(Clone)]
1391pub struct CircuitTemplate {
1392 name: String,
1393 pattern: Vec<String>, replacement: Vec<String>,
1395 cost_reduction: f64,
1396}
1397
1398impl TemplateMatching {
1399 #[must_use]
1400 pub fn new() -> Self {
1401 let templates = vec![
1402 CircuitTemplate {
1403 name: "H-X-H to Z".to_string(),
1404 pattern: vec!["H".to_string(), "X".to_string(), "H".to_string()],
1405 replacement: vec!["Z".to_string()],
1406 cost_reduction: 2.0,
1407 },
1408 CircuitTemplate {
1409 name: "CNOT-H-CNOT to CZ".to_string(),
1410 pattern: vec!["CNOT".to_string(), "H".to_string(), "CNOT".to_string()],
1411 replacement: vec!["CZ".to_string()],
1412 cost_reduction: 1.5,
1413 },
1414 CircuitTemplate {
1415 name: "Double CNOT elimination".to_string(),
1416 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
1417 replacement: vec![],
1418 cost_reduction: 2.0,
1419 },
1420 ];
1421
1422 Self { templates }
1423 }
1424
1425 #[must_use]
1426 pub const fn with_templates(templates: Vec<CircuitTemplate>) -> Self {
1427 Self { templates }
1428 }
1429}
1430
1431impl OptimizationPass for TemplateMatching {
1432 fn name(&self) -> &'static str {
1433 "Template Matching"
1434 }
1435
1436 fn apply_to_gates(
1437 &self,
1438 gates: Vec<Box<dyn GateOp>>,
1439 cost_model: &dyn CostModel,
1440 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1441 let mut optimized = gates;
1442 let mut changed = true;
1443
1444 while changed {
1445 changed = false;
1446 let original_cost = cost_model.gates_cost(&optimized);
1447
1448 for template in &self.templates {
1449 let result = self.apply_template(template, optimized.clone())?;
1450 let new_cost = cost_model.gates_cost(&result);
1451
1452 if new_cost < original_cost {
1453 optimized = result;
1454 changed = true;
1455 break;
1456 }
1457 }
1458 }
1459
1460 Ok(optimized)
1461 }
1462}
1463
1464impl TemplateMatching {
1465 fn apply_template(
1467 &self,
1468 template: &CircuitTemplate,
1469 gates: Vec<Box<dyn GateOp>>,
1470 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1471 let mut result = Vec::new();
1472 let mut i = 0;
1473
1474 while i < gates.len() {
1475 if let Some(replacement) = self.match_pattern_at_position(template, &gates, i)? {
1476 result.extend(replacement);
1478 i += template.pattern.len();
1479 } else {
1480 result.push(gates[i].clone());
1482 i += 1;
1483 }
1484 }
1485
1486 Ok(result)
1487 }
1488
1489 fn match_pattern_at_position(
1491 &self,
1492 template: &CircuitTemplate,
1493 gates: &[Box<dyn GateOp>],
1494 start: usize,
1495 ) -> QuantRS2Result<Option<Vec<Box<dyn GateOp>>>> {
1496 if start + template.pattern.len() > gates.len() {
1497 return Ok(None);
1498 }
1499
1500 let mut qubit_mapping = HashMap::new();
1502 let mut all_qubits = Vec::new();
1503 let mut is_match = true;
1504
1505 for (i, pattern_gate) in template.pattern.iter().enumerate() {
1506 let gate = &gates[start + i];
1507
1508 if !self.gate_matches_pattern(gate.as_ref(), pattern_gate, &qubit_mapping) {
1510 is_match = false;
1511 break;
1512 }
1513
1514 for qubit in gate.qubits() {
1516 if !all_qubits.contains(&qubit) {
1517 all_qubits.push(qubit);
1518 }
1519 }
1520 }
1521
1522 if !is_match {
1523 return Ok(None);
1524 }
1525
1526 if template
1528 .pattern
1529 .iter()
1530 .all(|p| p == "H" || p == "X" || p == "Y" || p == "Z" || p == "S" || p == "T")
1531 {
1532 let first_qubit = gates[start].qubits();
1534 if first_qubit.len() != 1 {
1535 return Ok(None);
1536 }
1537
1538 for i in 1..template.pattern.len() {
1539 let gate_qubits = gates[start + i].qubits();
1540 if gate_qubits != first_qubit {
1541 return Ok(None);
1542 }
1543 }
1544 }
1545
1546 qubit_mapping.insert("qubits".to_string(), all_qubits);
1548
1549 self.generate_replacement_gates(template, &qubit_mapping)
1551 }
1552
1553 fn gate_matches_pattern(
1555 &self,
1556 gate: &dyn GateOp,
1557 pattern: &str,
1558 qubit_mapping: &HashMap<String, Vec<QubitId>>,
1559 ) -> bool {
1560 gate.name() == pattern
1563 }
1564
1565 fn parse_pattern(&self, pattern: &str) -> Option<(String, String)> {
1567 if let Some(open_paren) = pattern.find('(') {
1568 if let Some(close_paren) = pattern.find(')') {
1569 let gate_name = pattern[..open_paren].to_string();
1570 let qubit_pattern = pattern[open_paren + 1..close_paren].to_string();
1571 return Some((gate_name, qubit_pattern));
1572 }
1573 }
1574 None
1575 }
1576
1577 fn generate_replacement_gates(
1579 &self,
1580 template: &CircuitTemplate,
1581 qubit_mapping: &HashMap<String, Vec<QubitId>>,
1582 ) -> QuantRS2Result<Option<Vec<Box<dyn GateOp>>>> {
1583 let mut replacement_gates = Vec::new();
1584
1585 let qubits: Vec<QubitId> = qubit_mapping
1587 .values()
1588 .flat_map(|v| v.iter().copied())
1589 .collect();
1590 let mut unique_qubits: Vec<QubitId> = Vec::new();
1591 for qubit in qubits {
1592 if !unique_qubits.contains(&qubit) {
1593 unique_qubits.push(qubit);
1594 }
1595 }
1596
1597 for replacement_pattern in &template.replacement {
1598 if let Some(gate) = self.create_simple_gate(replacement_pattern, &unique_qubits)? {
1599 replacement_gates.push(gate);
1600 }
1601 }
1602
1603 Ok(Some(replacement_gates))
1604 }
1605
1606 fn create_simple_gate(
1608 &self,
1609 pattern: &str,
1610 qubits: &[QubitId],
1611 ) -> QuantRS2Result<Option<Box<dyn GateOp>>> {
1612 if qubits.is_empty() {
1613 return Ok(None);
1614 }
1615
1616 match pattern {
1617 "H" => Ok(Some(Box::new(single::Hadamard { target: qubits[0] }))),
1618 "X" => Ok(Some(Box::new(single::PauliX { target: qubits[0] }))),
1619 "Y" => Ok(Some(Box::new(single::PauliY { target: qubits[0] }))),
1620 "Z" => Ok(Some(Box::new(single::PauliZ { target: qubits[0] }))),
1621 "S" => Ok(Some(Box::new(single::Phase { target: qubits[0] }))),
1622 "T" => Ok(Some(Box::new(single::T { target: qubits[0] }))),
1623 "CNOT" if qubits.len() >= 2 => Ok(Some(Box::new(multi::CNOT {
1624 control: qubits[0],
1625 target: qubits[1],
1626 }))),
1627 "CZ" if qubits.len() >= 2 => Ok(Some(Box::new(multi::CZ {
1628 control: qubits[0],
1629 target: qubits[1],
1630 }))),
1631 "SWAP" if qubits.len() >= 2 => Ok(Some(Box::new(multi::SWAP {
1632 qubit1: qubits[0],
1633 qubit2: qubits[1],
1634 }))),
1635 _ => Ok(None),
1636 }
1637 }
1638
1639 fn create_gate_from_pattern(
1641 &self,
1642 pattern: &str,
1643 qubit_mapping: &HashMap<String, Vec<QubitId>>,
1644 ) -> QuantRS2Result<Option<Box<dyn GateOp>>> {
1645 if let Some((gate_name, qubit_pattern)) = self.parse_pattern(pattern) {
1646 if let Some(qubits) = qubit_mapping.get(&qubit_pattern) {
1647 return Ok(Some(self.create_gate(&gate_name, qubits)?));
1648 }
1649 } else {
1650 if let Some((_, qubits)) = qubit_mapping.iter().next() {
1652 if !qubits.is_empty() {
1653 return Ok(Some(self.create_gate(pattern, &[qubits[0]])?));
1654 }
1655 }
1656 }
1657
1658 Ok(None)
1659 }
1660
1661 fn create_gate(&self, gate_name: &str, qubits: &[QubitId]) -> QuantRS2Result<Box<dyn GateOp>> {
1663 match (gate_name, qubits.len()) {
1664 ("H", 1) => Ok(Box::new(single::Hadamard { target: qubits[0] })),
1665 ("X", 1) => Ok(Box::new(single::PauliX { target: qubits[0] })),
1666 ("Y", 1) => Ok(Box::new(single::PauliY { target: qubits[0] })),
1667 ("Z", 1) => Ok(Box::new(single::PauliZ { target: qubits[0] })),
1668 ("S", 1) => Ok(Box::new(single::Phase { target: qubits[0] })),
1669 ("T", 1) => Ok(Box::new(single::T { target: qubits[0] })),
1670 ("CNOT", 2) => Ok(Box::new(multi::CNOT {
1671 control: qubits[0],
1672 target: qubits[1],
1673 })),
1674 ("CZ", 2) => Ok(Box::new(multi::CZ {
1675 control: qubits[0],
1676 target: qubits[1],
1677 })),
1678 ("SWAP", 2) => Ok(Box::new(multi::SWAP {
1679 qubit1: qubits[0],
1680 qubit2: qubits[1],
1681 })),
1682 _ => Err(QuantRS2Error::UnsupportedOperation(format!(
1683 "Cannot create gate {} with {} qubits",
1684 gate_name,
1685 qubits.len()
1686 ))),
1687 }
1688 }
1689
1690 #[must_use]
1692 pub fn with_advanced_templates() -> Self {
1693 let templates = vec![
1694 CircuitTemplate {
1696 name: "H-Z-H to X".to_string(),
1697 pattern: vec!["H".to_string(), "Z".to_string(), "H".to_string()],
1698 replacement: vec!["X".to_string()],
1699 cost_reduction: 2.0,
1700 },
1701 CircuitTemplate {
1702 name: "H-X-H to Z".to_string(),
1703 pattern: vec!["H".to_string(), "X".to_string(), "H".to_string()],
1704 replacement: vec!["Z".to_string()],
1705 cost_reduction: 2.0,
1706 },
1707 CircuitTemplate {
1709 name: "CNOT-CNOT elimination".to_string(),
1710 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
1711 replacement: vec![],
1712 cost_reduction: 2.0,
1713 },
1714 CircuitTemplate {
1716 name: "S-S to Z".to_string(),
1717 pattern: vec!["S".to_string(), "S".to_string()],
1718 replacement: vec!["Z".to_string()],
1719 cost_reduction: 1.0,
1720 },
1721 CircuitTemplate {
1722 name: "T-T-T-T to Identity".to_string(),
1723 pattern: vec![
1724 "T".to_string(),
1725 "T".to_string(),
1726 "T".to_string(),
1727 "T".to_string(),
1728 ],
1729 replacement: vec![],
1730 cost_reduction: 4.0,
1731 },
1732 CircuitTemplate {
1734 name: "CNOT-H-CNOT to CZ".to_string(),
1735 pattern: vec!["CNOT".to_string(), "H".to_string(), "CNOT".to_string()],
1736 replacement: vec!["CZ".to_string()],
1737 cost_reduction: 1.0,
1738 },
1739 CircuitTemplate {
1741 name: "SWAP via 3 CNOTs".to_string(),
1742 pattern: vec!["CNOT".to_string(), "CNOT".to_string(), "CNOT".to_string()],
1743 replacement: vec!["SWAP".to_string()],
1744 cost_reduction: 0.5, },
1746 ];
1747
1748 Self { templates }
1749 }
1750
1751 #[must_use]
1753 pub fn for_hardware(hardware: &str) -> Self {
1754 let templates = match hardware {
1755 "ibm" => vec![
1756 CircuitTemplate {
1757 name: "H-Z-H to X".to_string(),
1758 pattern: vec!["H".to_string(), "Z".to_string(), "H".to_string()],
1759 replacement: vec!["X".to_string()],
1760 cost_reduction: 2.0,
1761 },
1762 CircuitTemplate {
1763 name: "CNOT-CNOT elimination".to_string(),
1764 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
1765 replacement: vec![],
1766 cost_reduction: 2.0,
1767 },
1768 ],
1769 "google" => vec![CircuitTemplate {
1770 name: "CNOT to CZ with Hadamards".to_string(),
1771 pattern: vec!["CNOT".to_string()],
1772 replacement: vec!["H".to_string(), "CZ".to_string(), "H".to_string()],
1773 cost_reduction: -0.5, }],
1775 _ => Self::new().templates,
1776 };
1777
1778 Self { templates }
1779 }
1780}
1781
1782impl Default for TemplateMatching {
1783 fn default() -> Self {
1784 Self::new()
1785 }
1786}
1787
1788pub struct CircuitRewriting {
1790 rules: Vec<RewriteRule>,
1791 max_rewrites: usize,
1792}
1793
1794#[derive(Clone)]
1795pub struct RewriteRule {
1796 name: String,
1797 condition: fn(&[Box<dyn GateOp>]) -> bool,
1798 rewrite: fn(&[Box<dyn GateOp>]) -> Vec<Box<dyn GateOp>>,
1799}
1800
1801impl CircuitRewriting {
1802 #[must_use]
1803 pub const fn new(max_rewrites: usize) -> Self {
1804 let rules = vec![
1805 ];
1807
1808 Self {
1809 rules,
1810 max_rewrites,
1811 }
1812 }
1813}
1814
1815impl OptimizationPass for CircuitRewriting {
1816 fn name(&self) -> &'static str {
1817 "Circuit Rewriting"
1818 }
1819
1820 fn apply_to_gates(
1821 &self,
1822 gates: Vec<Box<dyn GateOp>>,
1823 _cost_model: &dyn CostModel,
1824 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1825 Ok(gates)
1827 }
1828}
1829
1830pub mod utils {
1832 use super::{get_gate_properties, GateOp, HashMap, OptimizationPass};
1833
1834 pub fn gates_cancel(gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
1836 if gate1.name() != gate2.name() || gate1.qubits() != gate2.qubits() {
1837 return false;
1838 }
1839
1840 let props = get_gate_properties(gate1);
1841 props.is_self_inverse
1842 }
1843
1844 pub fn is_identity_gate(gate: &dyn GateOp, tolerance: f64) -> bool {
1846 match gate.name() {
1847 "RX" | "RY" | "RZ" => {
1848 if let Ok(matrix) = gate.matrix() {
1850 (matrix[0].re - 1.0).abs() < tolerance && matrix[0].im.abs() < tolerance
1852 } else {
1853 false
1854 }
1855 }
1856 _ => false,
1857 }
1858 }
1859
1860 #[must_use]
1862 pub fn calculate_depth(gates: &[Box<dyn GateOp>]) -> usize {
1863 let mut qubit_depths: HashMap<u32, usize> = HashMap::new();
1864 let mut max_depth = 0;
1865
1866 for gate in gates {
1867 let gate_qubits = gate.qubits();
1868 let current_depth = gate_qubits
1869 .iter()
1870 .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
1871 .max()
1872 .unwrap_or(0);
1873
1874 let new_depth = current_depth + 1;
1875 for qubit in gate_qubits {
1876 qubit_depths.insert(qubit.id(), new_depth);
1877 }
1878
1879 max_depth = max_depth.max(new_depth);
1880 }
1881
1882 max_depth
1883 }
1884}