1use crate::optimization::cost_model::CostModel;
4use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
5use quantrs2_core::gate::{
6 multi,
7 single::{self},
8 GateOp,
9};
10use quantrs2_core::qubit::QubitId;
11use std::collections::{HashMap, HashSet};
12use std::f64::consts::PI;
13
14use super::OptimizationPass;
15
16pub struct PeepholeOptimization {
18 window_size: usize,
19 patterns: Vec<PeepholePattern>,
20}
21
22#[derive(Clone)]
23pub struct PeepholePattern {
24 name: String,
25 window_size: usize,
26 matcher: fn(&[Box<dyn GateOp>]) -> Option<Vec<Box<dyn GateOp>>>,
27}
28
29impl PeepholeOptimization {
30 #[must_use]
31 pub fn new(window_size: usize) -> Self {
32 let patterns = vec![
33 PeepholePattern {
35 name: "X-Y-X to -Y".to_string(),
36 window_size: 3,
37 matcher: |gates| {
38 if gates.len() >= 3 {
39 let g0 = &gates[0];
40 let g1 = &gates[1];
41 let g2 = &gates[2];
42
43 if g0.name() == "X"
44 && g2.name() == "X"
45 && g1.name() == "Y"
46 && g0.qubits() == g1.qubits()
47 && g1.qubits() == g2.qubits()
48 {
49 return Some(vec![g1.clone()]);
51 }
52 }
53 None
54 },
55 },
56 PeepholePattern {
58 name: "H-S-H simplification".to_string(),
59 window_size: 3,
60 matcher: |gates| {
61 if gates.len() >= 3 {
62 let g0 = &gates[0];
63 let g1 = &gates[1];
64 let g2 = &gates[2];
65
66 if g0.name() == "H"
67 && g2.name() == "H"
68 && g1.name() == "S"
69 && g0.qubits() == g1.qubits()
70 && g1.qubits() == g2.qubits()
71 {
72 let target = g0.qubits()[0];
73 return Some(vec![
74 Box::new(single::PauliX { target }) as Box<dyn GateOp>,
75 Box::new(single::RotationZ {
76 target,
77 theta: PI / 2.0,
78 }) as Box<dyn GateOp>,
79 Box::new(single::PauliX { target }) as Box<dyn GateOp>,
80 ]);
81 }
82 }
83 None
84 },
85 },
86 PeepholePattern {
88 name: "Euler angle optimization".to_string(),
89 window_size: 3,
90 matcher: |gates| {
91 if gates.len() >= 3 {
92 let g0 = &gates[0];
93 let g1 = &gates[1];
94 let g2 = &gates[2];
95
96 if g0.name() == "RZ"
97 && g1.name() == "RX"
98 && g2.name() == "RZ"
99 && g0.qubits() == g1.qubits()
100 && g1.qubits() == g2.qubits()
101 {
102 if let (Some(rz1), Some(rx), Some(rz2)) = (
103 g0.as_any().downcast_ref::<single::RotationZ>(),
104 g1.as_any().downcast_ref::<single::RotationX>(),
105 g2.as_any().downcast_ref::<single::RotationZ>(),
106 ) {
107 if rx.theta.abs() < 1e-10 {
109 let combined_angle = rz1.theta + rz2.theta;
110 if combined_angle.abs() < 1e-10 {
111 return Some(vec![]); }
113 return Some(vec![Box::new(single::RotationZ {
114 target: rz1.target,
115 theta: combined_angle,
116 })
117 as Box<dyn GateOp>]);
118 }
119 }
120 }
121 }
122 None
123 },
124 },
125 PeepholePattern {
127 name: "Phase gadget optimization".to_string(),
128 window_size: 3,
129 matcher: |gates| {
130 if gates.len() >= 3 {
131 let g0 = &gates[0];
132 let g1 = &gates[1];
133 let g2 = &gates[2];
134
135 if g0.name() == "CNOT" && g2.name() == "CNOT" && g1.name() == "RZ" {
136 if let (Some(cnot1), Some(rz), Some(cnot2)) = (
137 g0.as_any().downcast_ref::<multi::CNOT>(),
138 g1.as_any().downcast_ref::<single::RotationZ>(),
139 g2.as_any().downcast_ref::<multi::CNOT>(),
140 ) {
141 if cnot1.control == cnot2.control
142 && cnot1.target == cnot2.target
143 && rz.target == cnot1.target
144 {
145 return None;
147 }
148 }
149 }
150 }
151 None
152 },
153 },
154 PeepholePattern {
156 name: "Hadamard ladder".to_string(),
157 window_size: 4,
158 matcher: |gates| {
159 if gates.len() >= 4 {
160 if gates[0].name() == "H"
162 && gates[1].name() == "CNOT"
163 && gates[2].name() == "H"
164 && gates[3].name() == "CNOT"
165 {
166 let h1_target = gates[0].qubits()[0];
167 let h2_target = gates[2].qubits()[0];
168
169 if let (Some(cnot1), Some(cnot2)) = (
170 gates[1].as_any().downcast_ref::<multi::CNOT>(),
171 gates[3].as_any().downcast_ref::<multi::CNOT>(),
172 ) {
173 if h1_target == cnot1.control
174 && h2_target == cnot2.control
175 && cnot1.target == cnot2.target
176 {
177 return None; }
179 }
180 }
181 }
182 None
183 },
184 },
185 ];
186
187 Self {
188 window_size,
189 patterns,
190 }
191 }
192
193 fn apply_patterns(&self, window: &[Box<dyn GateOp>]) -> Option<Vec<Box<dyn GateOp>>> {
195 for pattern in &self.patterns {
196 if window.len() >= pattern.window_size {
197 if let Some(replacement) = (pattern.matcher)(window) {
198 return Some(replacement);
199 }
200 }
201 }
202 None
203 }
204}
205
206impl OptimizationPass for PeepholeOptimization {
207 fn name(&self) -> &'static str {
208 "Peephole Optimization"
209 }
210
211 fn apply_to_gates(
212 &self,
213 gates: Vec<Box<dyn GateOp>>,
214 _cost_model: &dyn CostModel,
215 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
216 let mut optimized = Vec::new();
217 let mut i = 0;
218
219 while i < gates.len() {
220 let mut matched = false;
221
222 for window_size in (2..=self.window_size).rev() {
223 if i + window_size <= gates.len() {
224 let window = &gates[i..i + window_size];
225
226 if let Some(replacement) = self.apply_patterns(window) {
227 optimized.extend(replacement);
228 i += window_size;
229 matched = true;
230 break;
231 }
232 }
233 }
234
235 if !matched {
236 optimized.push(gates[i].clone());
237 i += 1;
238 }
239 }
240
241 Ok(optimized)
242 }
243}
244
245pub struct TemplateMatching {
247 templates: Vec<CircuitTemplate>,
248}
249
250#[derive(Clone)]
251pub struct CircuitTemplate {
252 name: String,
253 pattern: Vec<String>,
254 replacement: Vec<String>,
255 cost_reduction: f64,
256}
257
258impl TemplateMatching {
259 #[must_use]
260 pub fn new() -> Self {
261 let templates = vec![
262 CircuitTemplate {
263 name: "H-Z-H to X".to_string(),
264 pattern: vec!["H".to_string(), "Z".to_string(), "H".to_string()],
265 replacement: vec!["X".to_string()],
266 cost_reduction: 2.0,
267 },
268 CircuitTemplate {
269 name: "H-X-H to Z".to_string(),
270 pattern: vec!["H".to_string(), "X".to_string(), "H".to_string()],
271 replacement: vec!["Z".to_string()],
272 cost_reduction: 2.0,
273 },
274 CircuitTemplate {
275 name: "CNOT-H-CNOT to CZ".to_string(),
276 pattern: vec!["CNOT".to_string(), "H".to_string(), "CNOT".to_string()],
277 replacement: vec!["CZ".to_string()],
278 cost_reduction: 1.5,
279 },
280 CircuitTemplate {
281 name: "Double CNOT elimination".to_string(),
282 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
283 replacement: vec![],
284 cost_reduction: 2.0,
285 },
286 CircuitTemplate {
287 name: "S-S to Z".to_string(),
288 pattern: vec!["S".to_string(), "S".to_string()],
289 replacement: vec!["Z".to_string()],
290 cost_reduction: 1.0,
291 },
292 ];
293
294 Self { templates }
295 }
296
297 #[must_use]
298 pub const fn with_templates(templates: Vec<CircuitTemplate>) -> Self {
299 Self { templates }
300 }
301
302 #[must_use]
304 pub fn with_advanced_templates() -> Self {
305 let templates = vec![
306 CircuitTemplate {
307 name: "H-Z-H to X".to_string(),
308 pattern: vec!["H".to_string(), "Z".to_string(), "H".to_string()],
309 replacement: vec!["X".to_string()],
310 cost_reduction: 2.0,
311 },
312 CircuitTemplate {
313 name: "H-X-H to Z".to_string(),
314 pattern: vec!["H".to_string(), "X".to_string(), "H".to_string()],
315 replacement: vec!["Z".to_string()],
316 cost_reduction: 2.0,
317 },
318 CircuitTemplate {
319 name: "CNOT-CNOT elimination".to_string(),
320 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
321 replacement: vec![],
322 cost_reduction: 2.0,
323 },
324 CircuitTemplate {
325 name: "S-S to Z".to_string(),
326 pattern: vec!["S".to_string(), "S".to_string()],
327 replacement: vec!["Z".to_string()],
328 cost_reduction: 1.0,
329 },
330 CircuitTemplate {
331 name: "T-T-T-T to Identity".to_string(),
332 pattern: vec![
333 "T".to_string(),
334 "T".to_string(),
335 "T".to_string(),
336 "T".to_string(),
337 ],
338 replacement: vec![],
339 cost_reduction: 4.0,
340 },
341 CircuitTemplate {
342 name: "CNOT-H-CNOT to CZ".to_string(),
343 pattern: vec!["CNOT".to_string(), "H".to_string(), "CNOT".to_string()],
344 replacement: vec!["CZ".to_string()],
345 cost_reduction: 1.0,
346 },
347 CircuitTemplate {
348 name: "SWAP via 3 CNOTs".to_string(),
349 pattern: vec!["CNOT".to_string(), "CNOT".to_string(), "CNOT".to_string()],
350 replacement: vec!["SWAP".to_string()],
351 cost_reduction: 0.5,
352 },
353 ];
354
355 Self { templates }
356 }
357
358 #[must_use]
360 pub fn for_hardware(hardware: &str) -> Self {
361 let templates = match hardware {
362 "ibm" => vec![
363 CircuitTemplate {
364 name: "H-Z-H to X".to_string(),
365 pattern: vec!["H".to_string(), "Z".to_string(), "H".to_string()],
366 replacement: vec!["X".to_string()],
367 cost_reduction: 2.0,
368 },
369 CircuitTemplate {
370 name: "CNOT-CNOT elimination".to_string(),
371 pattern: vec!["CNOT".to_string(), "CNOT".to_string()],
372 replacement: vec![],
373 cost_reduction: 2.0,
374 },
375 ],
376 "google" => vec![CircuitTemplate {
377 name: "CNOT to CZ with Hadamards".to_string(),
378 pattern: vec!["CNOT".to_string()],
379 replacement: vec!["H".to_string(), "CZ".to_string(), "H".to_string()],
380 cost_reduction: -0.5,
381 }],
382 _ => Self::new().templates,
383 };
384
385 Self { templates }
386 }
387}
388
389impl OptimizationPass for TemplateMatching {
390 fn name(&self) -> &'static str {
391 "Template Matching"
392 }
393
394 fn apply_to_gates(
395 &self,
396 gates: Vec<Box<dyn GateOp>>,
397 cost_model: &dyn CostModel,
398 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
399 let mut optimized = gates;
400 let mut changed = true;
401
402 while changed {
403 changed = false;
404 let original_cost = cost_model.gates_cost(&optimized);
405
406 for template in &self.templates {
407 let result = self.apply_template(template, optimized.clone())?;
408 let new_cost = cost_model.gates_cost(&result);
409
410 if new_cost < original_cost {
411 optimized = result;
412 changed = true;
413 break;
414 }
415 }
416 }
417
418 Ok(optimized)
419 }
420}
421
422impl TemplateMatching {
423 fn apply_template(
424 &self,
425 template: &CircuitTemplate,
426 gates: Vec<Box<dyn GateOp>>,
427 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
428 let mut result = Vec::new();
429 let mut i = 0;
430
431 while i < gates.len() {
432 if let Some(replacement) = self.match_pattern_at_position(template, &gates, i)? {
433 result.extend(replacement);
434 i += template.pattern.len();
435 } else {
436 result.push(gates[i].clone());
437 i += 1;
438 }
439 }
440
441 Ok(result)
442 }
443
444 fn match_pattern_at_position(
445 &self,
446 template: &CircuitTemplate,
447 gates: &[Box<dyn GateOp>],
448 start: usize,
449 ) -> QuantRS2Result<Option<Vec<Box<dyn GateOp>>>> {
450 if start + template.pattern.len() > gates.len() {
451 return Ok(None);
452 }
453
454 let mut qubit_mapping = HashMap::new();
455 let mut all_qubits = Vec::new();
456 let mut is_match = true;
457
458 for (i, pattern_gate) in template.pattern.iter().enumerate() {
459 let gate = &gates[start + i];
460
461 if !self.gate_matches_pattern(gate.as_ref(), pattern_gate, &qubit_mapping) {
462 is_match = false;
463 break;
464 }
465
466 for qubit in gate.qubits() {
467 if !all_qubits.contains(&qubit) {
468 all_qubits.push(qubit);
469 }
470 }
471 }
472
473 if !is_match {
474 return Ok(None);
475 }
476
477 if template
478 .pattern
479 .iter()
480 .all(|p| p == "H" || p == "X" || p == "Y" || p == "Z" || p == "S" || p == "T")
481 {
482 let first_qubit = gates[start].qubits();
483 if first_qubit.len() != 1 {
484 return Ok(None);
485 }
486
487 for i in 1..template.pattern.len() {
488 let gate_qubits = gates[start + i].qubits();
489 if gate_qubits != first_qubit {
490 return Ok(None);
491 }
492 }
493 }
494
495 qubit_mapping.insert("qubits".to_string(), all_qubits);
496 self.generate_replacement_gates(template, &qubit_mapping)
497 }
498
499 fn gate_matches_pattern(
500 &self,
501 gate: &dyn GateOp,
502 pattern: &str,
503 _qubit_mapping: &HashMap<String, Vec<QubitId>>,
504 ) -> bool {
505 gate.name() == pattern
506 }
507
508 fn generate_replacement_gates(
509 &self,
510 template: &CircuitTemplate,
511 qubit_mapping: &HashMap<String, Vec<QubitId>>,
512 ) -> QuantRS2Result<Option<Vec<Box<dyn GateOp>>>> {
513 let mut replacement_gates = Vec::new();
514
515 let qubits: Vec<QubitId> = qubit_mapping
516 .values()
517 .flat_map(|v| v.iter().copied())
518 .collect();
519 let mut unique_qubits: Vec<QubitId> = Vec::new();
520 for qubit in qubits {
521 if !unique_qubits.contains(&qubit) {
522 unique_qubits.push(qubit);
523 }
524 }
525
526 for replacement_pattern in &template.replacement {
527 if let Some(gate) = self.create_simple_gate(replacement_pattern, &unique_qubits)? {
528 replacement_gates.push(gate);
529 }
530 }
531
532 Ok(Some(replacement_gates))
533 }
534
535 fn create_simple_gate(
536 &self,
537 pattern: &str,
538 qubits: &[QubitId],
539 ) -> QuantRS2Result<Option<Box<dyn GateOp>>> {
540 if qubits.is_empty() {
541 return Ok(None);
542 }
543
544 match pattern {
545 "H" => Ok(Some(Box::new(single::Hadamard { target: qubits[0] }))),
546 "X" => Ok(Some(Box::new(single::PauliX { target: qubits[0] }))),
547 "Y" => Ok(Some(Box::new(single::PauliY { target: qubits[0] }))),
548 "Z" => Ok(Some(Box::new(single::PauliZ { target: qubits[0] }))),
549 "S" => Ok(Some(Box::new(single::Phase { target: qubits[0] }))),
550 "T" => Ok(Some(Box::new(single::T { target: qubits[0] }))),
551 "CNOT" if qubits.len() >= 2 => Ok(Some(Box::new(multi::CNOT {
552 control: qubits[0],
553 target: qubits[1],
554 }))),
555 "CZ" if qubits.len() >= 2 => Ok(Some(Box::new(multi::CZ {
556 control: qubits[0],
557 target: qubits[1],
558 }))),
559 "SWAP" if qubits.len() >= 2 => Ok(Some(Box::new(multi::SWAP {
560 qubit1: qubits[0],
561 qubit2: qubits[1],
562 }))),
563 _ => Ok(None),
564 }
565 }
566
567 fn create_gate(&self, gate_name: &str, qubits: &[QubitId]) -> QuantRS2Result<Box<dyn GateOp>> {
569 match (gate_name, qubits.len()) {
570 ("H", 1) => Ok(Box::new(single::Hadamard { target: qubits[0] })),
571 ("X", 1) => Ok(Box::new(single::PauliX { target: qubits[0] })),
572 ("Y", 1) => Ok(Box::new(single::PauliY { target: qubits[0] })),
573 ("Z", 1) => Ok(Box::new(single::PauliZ { target: qubits[0] })),
574 ("S", 1) => Ok(Box::new(single::Phase { target: qubits[0] })),
575 ("T", 1) => Ok(Box::new(single::T { target: qubits[0] })),
576 ("CNOT", 2) => Ok(Box::new(multi::CNOT {
577 control: qubits[0],
578 target: qubits[1],
579 })),
580 ("CZ", 2) => Ok(Box::new(multi::CZ {
581 control: qubits[0],
582 target: qubits[1],
583 })),
584 ("SWAP", 2) => Ok(Box::new(multi::SWAP {
585 qubit1: qubits[0],
586 qubit2: qubits[1],
587 })),
588 _ => Err(QuantRS2Error::UnsupportedOperation(format!(
589 "Cannot create gate {} with {} qubits",
590 gate_name,
591 qubits.len()
592 ))),
593 }
594 }
595}
596
597impl Default for TemplateMatching {
598 fn default() -> Self {
599 Self::new()
600 }
601}
602
603pub fn single_qubit_of(g: &dyn GateOp) -> Option<QubitId> {
607 let qs = g.qubits();
608 if qs.len() == 1 {
609 Some(qs[0])
610 } else {
611 None
612 }
613}
614
615pub fn all_same_single_qubit(gates: &[Box<dyn GateOp>]) -> bool {
617 let first = match single_qubit_of(gates[0].as_ref()) {
618 Some(q) => q,
619 None => return false,
620 };
621 gates[1..]
622 .iter()
623 .all(|g| single_qubit_of(g.as_ref()) == Some(first))
624}
625
626pub fn extract_rz_angle(g: &dyn GateOp) -> Option<f64> {
635 if g.name() != "RZ" {
636 return None;
637 }
638 g.matrix().ok().map(|m| (m[3] * m[0].conj()).arg())
639}
640
641pub fn extract_rx_angle(g: &dyn GateOp) -> Option<f64> {
643 if g.name() != "RX" {
644 return None;
645 }
646 g.matrix().ok().map(|m| {
647 let sin_half = -m[1].im;
648 let cos_half = m[0].re;
649 2.0 * sin_half.atan2(cos_half)
650 })
651}
652
653pub fn extract_ry_angle(g: &dyn GateOp) -> Option<f64> {
655 if g.name() != "RY" {
656 return None;
657 }
658 g.matrix().ok().map(|m| {
659 let sin_half = -m[1].re;
660 let cos_half = m[0].re;
661 2.0 * sin_half.atan2(cos_half)
662 })
663}
664
665pub fn normalise_angle(theta: f64) -> f64 {
667 use std::f64::consts::TAU;
668 let t = theta % TAU;
669 if t > PI {
670 t - TAU
671 } else if t <= -PI {
672 t + TAU
673 } else {
674 t
675 }
676}
677
678pub fn is_identity_angle(theta: f64, eps: f64) -> bool {
680 normalise_angle(theta).abs() < eps
681}
682
683fn default_rewrite_rules() -> Vec<RewriteRule> {
684 let hxh_to_z = RewriteRule {
688 name: "H-X-H to Z".to_string(),
689 window_size: 3,
690 condition: |w| {
691 w[0].name() == "H"
692 && w[1].name() == "X"
693 && w[2].name() == "H"
694 && all_same_single_qubit(w)
695 },
696 rewrite: |w| {
697 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
698 vec![Box::new(single::PauliZ { target: t }) as Box<dyn GateOp>]
699 },
700 };
701
702 let hzh_to_x = RewriteRule {
704 name: "H-Z-H to X".to_string(),
705 window_size: 3,
706 condition: |w| {
707 w[0].name() == "H"
708 && w[1].name() == "Z"
709 && w[2].name() == "H"
710 && all_same_single_qubit(w)
711 },
712 rewrite: |w| {
713 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
714 vec![Box::new(single::PauliX { target: t }) as Box<dyn GateOp>]
715 },
716 };
717
718 let hyh_to_y = RewriteRule {
720 name: "H-Y-H to Y (global phase)".to_string(),
721 window_size: 3,
722 condition: |w| {
723 w[0].name() == "H"
724 && w[1].name() == "Y"
725 && w[2].name() == "H"
726 && all_same_single_qubit(w)
727 },
728 rewrite: |w| {
729 let t = single_qubit_of(w[1].as_ref()).unwrap_or_else(|| w[1].qubits()[0]);
730 vec![Box::new(single::PauliY { target: t }) as Box<dyn GateOp>]
731 },
732 };
733
734 let xzx_to_z = RewriteRule {
736 name: "X-Z-X to Z (global phase)".to_string(),
737 window_size: 3,
738 condition: |w| {
739 w[0].name() == "X"
740 && w[1].name() == "Z"
741 && w[2].name() == "X"
742 && all_same_single_qubit(w)
743 },
744 rewrite: |w| {
745 let t = single_qubit_of(w[1].as_ref()).unwrap_or_else(|| w[1].qubits()[0]);
746 vec![Box::new(single::PauliZ { target: t }) as Box<dyn GateOp>]
747 },
748 };
749
750 let zxz_to_x = RewriteRule {
752 name: "Z-X-Z to X (global phase)".to_string(),
753 window_size: 3,
754 condition: |w| {
755 w[0].name() == "Z"
756 && w[1].name() == "X"
757 && w[2].name() == "Z"
758 && all_same_single_qubit(w)
759 },
760 rewrite: |w| {
761 let t = single_qubit_of(w[1].as_ref()).unwrap_or_else(|| w[1].qubits()[0]);
762 vec![Box::new(single::PauliX { target: t }) as Box<dyn GateOp>]
763 },
764 };
765
766 let hh = RewriteRule {
768 name: "H-H cancel".to_string(),
769 window_size: 2,
770 condition: |w| w[0].name() == "H" && w[1].name() == "H" && all_same_single_qubit(w),
771 rewrite: |_w| vec![],
772 };
773 let xx = RewriteRule {
774 name: "X-X cancel".to_string(),
775 window_size: 2,
776 condition: |w| w[0].name() == "X" && w[1].name() == "X" && all_same_single_qubit(w),
777 rewrite: |_w| vec![],
778 };
779 let yy = RewriteRule {
780 name: "Y-Y cancel".to_string(),
781 window_size: 2,
782 condition: |w| w[0].name() == "Y" && w[1].name() == "Y" && all_same_single_qubit(w),
783 rewrite: |_w| vec![],
784 };
785 let zz = RewriteRule {
786 name: "Z-Z cancel".to_string(),
787 window_size: 2,
788 condition: |w| w[0].name() == "Z" && w[1].name() == "Z" && all_same_single_qubit(w),
789 rewrite: |_w| vec![],
790 };
791
792 let ss_to_z = RewriteRule {
794 name: "S-S to Z".to_string(),
795 window_size: 2,
796 condition: |w| w[0].name() == "S" && w[1].name() == "S" && all_same_single_qubit(w),
797 rewrite: |w| {
798 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
799 vec![Box::new(single::PauliZ { target: t }) as Box<dyn GateOp>]
800 },
801 };
802
803 let tt_to_s = RewriteRule {
805 name: "T-T to S".to_string(),
806 window_size: 2,
807 condition: |w| w[0].name() == "T" && w[1].name() == "T" && all_same_single_qubit(w),
808 rewrite: |w| {
809 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
810 vec![Box::new(single::Phase { target: t }) as Box<dyn GateOp>]
811 },
812 };
813
814 let cnot_cnot = RewriteRule {
816 name: "CNOT-CNOT cancel".to_string(),
817 window_size: 2,
818 condition: |w| {
819 if w[0].name() != "CNOT" || w[1].name() != "CNOT" {
820 return false;
821 }
822 match (
823 w[0].as_any().downcast_ref::<multi::CNOT>(),
824 w[1].as_any().downcast_ref::<multi::CNOT>(),
825 ) {
826 (Some(c1), Some(c2)) => c1.control == c2.control && c1.target == c2.target,
827 _ => false,
828 }
829 },
830 rewrite: |_w| vec![],
831 };
832
833 let cz_cz = RewriteRule {
835 name: "CZ-CZ cancel".to_string(),
836 window_size: 2,
837 condition: |w| {
838 if w[0].name() != "CZ" || w[1].name() != "CZ" {
839 return false;
840 }
841 let q0 = w[0].qubits();
842 let q1 = w[1].qubits();
843 if q0.len() != 2 || q1.len() != 2 {
844 return false;
845 }
846 (q0[0] == q1[0] && q0[1] == q1[1]) || (q0[0] == q1[1] && q0[1] == q1[0])
847 },
848 rewrite: |_w| vec![],
849 };
850
851 let rx_merge = RewriteRule {
853 name: "RX-RX merge".to_string(),
854 window_size: 2,
855 condition: |w| {
856 w[0].name() == "RX"
857 && w[1].name() == "RX"
858 && all_same_single_qubit(w)
859 && extract_rx_angle(w[0].as_ref()).is_some()
860 && extract_rx_angle(w[1].as_ref()).is_some()
861 },
862 rewrite: |w| {
863 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
864 let a = extract_rx_angle(w[0].as_ref()).unwrap_or(0.0);
865 let b = extract_rx_angle(w[1].as_ref()).unwrap_or(0.0);
866 let sum = normalise_angle(a + b);
867 if is_identity_angle(sum, 1e-10) {
868 vec![]
869 } else {
870 vec![Box::new(single::RotationX {
871 target: t,
872 theta: sum,
873 }) as Box<dyn GateOp>]
874 }
875 },
876 };
877
878 let ry_merge = RewriteRule {
879 name: "RY-RY merge".to_string(),
880 window_size: 2,
881 condition: |w| {
882 w[0].name() == "RY"
883 && w[1].name() == "RY"
884 && all_same_single_qubit(w)
885 && extract_ry_angle(w[0].as_ref()).is_some()
886 && extract_ry_angle(w[1].as_ref()).is_some()
887 },
888 rewrite: |w| {
889 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
890 let a = extract_ry_angle(w[0].as_ref()).unwrap_or(0.0);
891 let b = extract_ry_angle(w[1].as_ref()).unwrap_or(0.0);
892 let sum = normalise_angle(a + b);
893 if is_identity_angle(sum, 1e-10) {
894 vec![]
895 } else {
896 vec![Box::new(single::RotationY {
897 target: t,
898 theta: sum,
899 }) as Box<dyn GateOp>]
900 }
901 },
902 };
903
904 let rz_merge = RewriteRule {
905 name: "RZ-RZ merge".to_string(),
906 window_size: 2,
907 condition: |w| {
908 w[0].name() == "RZ"
909 && w[1].name() == "RZ"
910 && all_same_single_qubit(w)
911 && extract_rz_angle(w[0].as_ref()).is_some()
912 && extract_rz_angle(w[1].as_ref()).is_some()
913 },
914 rewrite: |w| {
915 let t = single_qubit_of(w[0].as_ref()).unwrap_or_else(|| w[0].qubits()[0]);
916 let a = extract_rz_angle(w[0].as_ref()).unwrap_or(0.0);
917 let b = extract_rz_angle(w[1].as_ref()).unwrap_or(0.0);
918 let sum = normalise_angle(a + b);
919 if is_identity_angle(sum, 1e-10) {
920 vec![]
921 } else {
922 vec![Box::new(single::RotationZ {
923 target: t,
924 theta: sum,
925 }) as Box<dyn GateOp>]
926 }
927 },
928 };
929
930 vec![
931 hxh_to_z, hzh_to_x, hyh_to_y, xzx_to_z, zxz_to_x, hh, xx, yy, zz, ss_to_z, tt_to_s,
932 cnot_cnot, cz_cz, rx_merge, ry_merge, rz_merge,
933 ]
934}
935
936pub struct CircuitRewriting {
938 rules: Vec<RewriteRule>,
939 max_rewrites: usize,
940}
941
942#[derive(Clone)]
944pub struct RewriteRule {
945 name: String,
947 window_size: usize,
949 condition: fn(&[Box<dyn GateOp>]) -> bool,
951 rewrite: fn(&[Box<dyn GateOp>]) -> Vec<Box<dyn GateOp>>,
953}
954
955impl CircuitRewriting {
956 #[must_use]
958 pub fn new(max_rewrites: usize) -> Self {
959 Self {
960 rules: default_rewrite_rules(),
961 max_rewrites,
962 }
963 }
964
965 #[must_use]
967 pub fn with_rules(rules: Vec<RewriteRule>, max_rewrites: usize) -> Self {
968 Self {
969 rules,
970 max_rewrites,
971 }
972 }
973
974 fn try_apply_at(
976 &self,
977 gates: &[Box<dyn GateOp>],
978 pos: usize,
979 ) -> Option<(usize, Vec<Box<dyn GateOp>>)> {
980 for rule in &self.rules {
981 let end = pos + rule.window_size;
982 if end > gates.len() {
983 continue;
984 }
985 let window = &gates[pos..end];
986 if (rule.condition)(window) {
987 return Some((rule.window_size, (rule.rewrite)(window)));
988 }
989 }
990 None
991 }
992
993 fn scan_once(&self, gates: Vec<Box<dyn GateOp>>) -> (Vec<Box<dyn GateOp>>, bool) {
995 let mut out: Vec<Box<dyn GateOp>> = Vec::with_capacity(gates.len());
996 let mut i = 0;
997 let mut fired = false;
998
999 while i < gates.len() {
1000 if let Some((consumed, replacement)) = self.try_apply_at(&gates, i) {
1001 out.extend(replacement);
1002 i += consumed;
1003 fired = true;
1004 } else {
1005 out.push(gates[i].clone());
1006 i += 1;
1007 }
1008 }
1009
1010 (out, fired)
1011 }
1012}
1013
1014impl OptimizationPass for CircuitRewriting {
1015 fn name(&self) -> &'static str {
1016 "Circuit Rewriting"
1017 }
1018
1019 fn apply_to_gates(
1020 &self,
1021 gates: Vec<Box<dyn GateOp>>,
1022 _cost_model: &dyn CostModel,
1023 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1024 let mut current = gates;
1025 let mut passes_done = 0;
1026
1027 loop {
1028 if passes_done >= self.max_rewrites {
1029 break;
1030 }
1031 let (next, fired) = self.scan_once(current);
1032 current = next;
1033 if !fired {
1034 break;
1035 }
1036 passes_done += 1;
1037 }
1038
1039 Ok(current)
1040 }
1041}
1042
1043pub struct ParallelizationPass;
1049
1050impl ParallelizationPass {
1051 #[must_use]
1053 pub const fn new() -> Self {
1054 Self
1055 }
1056}
1057
1058impl Default for ParallelizationPass {
1059 fn default() -> Self {
1060 Self::new()
1061 }
1062}
1063
1064impl OptimizationPass for ParallelizationPass {
1065 fn name(&self) -> &'static str {
1066 "Parallelization (ASAP)"
1067 }
1068
1069 fn apply_to_gates(
1070 &self,
1071 gates: Vec<Box<dyn GateOp>>,
1072 _cost_model: &dyn CostModel,
1073 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1074 Ok(parallelize_gates(gates))
1075 }
1076}
1077
1078#[must_use]
1085pub fn parallelize_gates(gates: Vec<Box<dyn GateOp>>) -> Vec<Box<dyn GateOp>> {
1086 let n = gates.len();
1087 if n == 0 {
1088 return gates;
1089 }
1090
1091 let qubit_sets: Vec<HashSet<u32>> = gates
1093 .iter()
1094 .map(|g| g.qubits().into_iter().map(|q| q.id()).collect())
1095 .collect();
1096
1097 let mut in_degree = vec![0usize; n];
1098 let mut predecessors: Vec<HashSet<usize>> = vec![HashSet::new(); n];
1099 let mut successors: Vec<Vec<usize>> = vec![Vec::new(); n];
1100
1101 let mut last_gate_on_qubit: HashMap<u32, usize> = HashMap::new();
1102
1103 for j in 0..n {
1104 for &qid in &qubit_sets[j] {
1105 if let Some(&i) = last_gate_on_qubit.get(&qid) {
1106 if predecessors[j].insert(i) {
1107 successors[i].push(j);
1108 in_degree[j] += 1;
1109 }
1110 }
1111 }
1112 for &qid in &qubit_sets[j] {
1113 last_gate_on_qubit.insert(qid, j);
1114 }
1115 }
1116
1117 let mut result: Vec<Box<dyn GateOp>> = Vec::with_capacity(n);
1119 let mut ready: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
1120
1121 while !ready.is_empty() {
1122 ready.sort_unstable();
1123 let layer = std::mem::take(&mut ready);
1124 for &idx in &layer {
1125 result.push(gates[idx].clone());
1126 for &succ in &successors[idx] {
1127 in_degree[succ] -= 1;
1128 if in_degree[succ] == 0 {
1129 ready.push(succ);
1130 }
1131 }
1132 }
1133 }
1134
1135 if result.len() != n {
1138 gates
1139 } else {
1140 result
1141 }
1142}
1143
1144pub mod utils {
1146 use super::{GateOp, HashMap};
1147 use crate::optimization::gate_properties::get_gate_properties;
1148
1149 pub fn gates_cancel(gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
1151 if gate1.name() != gate2.name() || gate1.qubits() != gate2.qubits() {
1152 return false;
1153 }
1154
1155 let props = get_gate_properties(gate1);
1156 props.is_self_inverse
1157 }
1158
1159 pub fn is_identity_gate(gate: &dyn GateOp, tolerance: f64) -> bool {
1161 match gate.name() {
1162 "RX" | "RY" | "RZ" => {
1163 if let Ok(matrix) = gate.matrix() {
1164 (matrix[0].re - 1.0).abs() < tolerance && matrix[0].im.abs() < tolerance
1165 } else {
1166 false
1167 }
1168 }
1169 _ => false,
1170 }
1171 }
1172
1173 #[must_use]
1175 pub fn calculate_depth(gates: &[Box<dyn GateOp>]) -> usize {
1176 let mut qubit_depths: HashMap<u32, usize> = HashMap::new();
1177 let mut max_depth = 0;
1178
1179 for gate in gates {
1180 let gate_qubits = gate.qubits();
1181 let current_depth = gate_qubits
1182 .iter()
1183 .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
1184 .max()
1185 .unwrap_or(0);
1186
1187 let new_depth = current_depth + 1;
1188 for qubit in gate_qubits {
1189 qubit_depths.insert(qubit.id(), new_depth);
1190 }
1191
1192 max_depth = max_depth.max(new_depth);
1193 }
1194
1195 max_depth
1196 }
1197}
1198
1199#[cfg(test)]
1202mod rewriting_tests {
1203 use super::*;
1204 use quantrs2_core::gate::single::{
1205 Hadamard, PauliX, PauliY, PauliZ, Phase, RotationX, RotationY, RotationZ, T,
1206 };
1207 use quantrs2_core::qubit::QubitId;
1208
1209 fn q(id: u32) -> QubitId {
1210 QubitId::new(id)
1211 }
1212
1213 fn cost() -> crate::optimization::cost_model::AbstractCostModel {
1214 crate::optimization::cost_model::AbstractCostModel::new(
1215 crate::optimization::cost_model::CostWeights::default(),
1216 )
1217 }
1218
1219 #[test]
1220 fn test_hh_cancels() {
1221 let pass = CircuitRewriting::new(10);
1222 let q0 = q(0);
1223 let gates: Vec<Box<dyn GateOp>> = vec![
1224 Box::new(Hadamard { target: q0 }),
1225 Box::new(Hadamard { target: q0 }),
1226 ];
1227 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1228 assert!(result.is_empty(), "H-H should cancel to identity");
1229 }
1230
1231 #[test]
1232 fn test_hh_different_qubits_no_cancel() {
1233 let pass = CircuitRewriting::new(10);
1234 let gates: Vec<Box<dyn GateOp>> = vec![
1235 Box::new(Hadamard { target: q(0) }),
1236 Box::new(Hadamard { target: q(1) }),
1237 ];
1238 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1239 assert_eq!(result.len(), 2, "H on different qubits must not cancel");
1240 }
1241
1242 #[test]
1243 fn test_ss_to_z() {
1244 let pass = CircuitRewriting::new(10);
1245 let q0 = q(0);
1246 let gates: Vec<Box<dyn GateOp>> = vec![
1247 Box::new(Phase { target: q0 }),
1248 Box::new(Phase { target: q0 }),
1249 ];
1250 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1251 assert_eq!(result.len(), 1, "S-S should produce one gate");
1252 assert_eq!(result[0].name(), "Z", "S-S should produce Z");
1253 }
1254
1255 #[test]
1256 fn test_tt_to_s() {
1257 let pass = CircuitRewriting::new(10);
1258 let q0 = q(0);
1259 let gates: Vec<Box<dyn GateOp>> =
1260 vec![Box::new(T { target: q0 }), Box::new(T { target: q0 })];
1261 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1262 assert_eq!(result.len(), 1, "T-T should produce one gate");
1263 assert_eq!(result[0].name(), "S", "T-T should produce S");
1264 }
1265
1266 #[test]
1267 fn test_rz_rz_merge() {
1268 let pass = CircuitRewriting::new(10);
1269 let q0 = q(0);
1270 let theta1 = PI / 4.0;
1271 let theta2 = PI / 4.0;
1272 let gates: Vec<Box<dyn GateOp>> = vec![
1273 Box::new(RotationZ {
1274 target: q0,
1275 theta: theta1,
1276 }),
1277 Box::new(RotationZ {
1278 target: q0,
1279 theta: theta2,
1280 }),
1281 ];
1282 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1283 assert_eq!(result.len(), 1, "RZ-RZ should merge to one gate");
1284 assert_eq!(result[0].name(), "RZ");
1285 let merged = result[0]
1286 .as_any()
1287 .downcast_ref::<RotationZ>()
1288 .expect("should be RotationZ");
1289 let expected = normalise_angle(theta1 + theta2);
1290 assert!(
1291 (merged.theta - expected).abs() < 1e-9,
1292 "merged angle {:.6} != expected {:.6}",
1293 merged.theta,
1294 expected
1295 );
1296 }
1297
1298 #[test]
1299 fn test_rz_rz_cancel() {
1300 let pass = CircuitRewriting::new(10);
1301 let q0 = q(0);
1302 let gates: Vec<Box<dyn GateOp>> = vec![
1303 Box::new(RotationZ {
1304 target: q0,
1305 theta: PI,
1306 }),
1307 Box::new(RotationZ {
1308 target: q0,
1309 theta: PI,
1310 }),
1311 ];
1312 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1313 assert!(result.is_empty(), "RZ(π)+RZ(π) should cancel");
1314 }
1315
1316 #[test]
1317 fn test_rx_rx_merge() {
1318 let pass = CircuitRewriting::new(10);
1319 let q0 = q(0);
1320 let gates: Vec<Box<dyn GateOp>> = vec![
1321 Box::new(RotationX {
1322 target: q0,
1323 theta: PI / 3.0,
1324 }),
1325 Box::new(RotationX {
1326 target: q0,
1327 theta: PI / 6.0,
1328 }),
1329 ];
1330 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1331 assert_eq!(result.len(), 1, "RX-RX should merge");
1332 assert_eq!(result[0].name(), "RX");
1333 let merged = result[0]
1334 .as_any()
1335 .downcast_ref::<RotationX>()
1336 .expect("RotationX");
1337 let expected = normalise_angle(PI / 3.0 + PI / 6.0);
1338 assert!((merged.theta - expected).abs() < 1e-9);
1339 }
1340
1341 #[test]
1342 fn test_ry_ry_cancel() {
1343 let pass = CircuitRewriting::new(10);
1344 let q0 = q(0);
1345 let gates: Vec<Box<dyn GateOp>> = vec![
1346 Box::new(RotationY {
1347 target: q0,
1348 theta: PI / 2.0,
1349 }),
1350 Box::new(RotationY {
1351 target: q0,
1352 theta: -PI / 2.0,
1353 }),
1354 ];
1355 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1356 assert!(result.is_empty(), "RY(π/2)+RY(-π/2) should cancel");
1357 }
1358
1359 #[test]
1360 fn test_hxh_to_z() {
1361 let pass = CircuitRewriting::new(10);
1362 let q0 = q(0);
1363 let gates: Vec<Box<dyn GateOp>> = vec![
1364 Box::new(Hadamard { target: q0 }),
1365 Box::new(PauliX { target: q0 }),
1366 Box::new(Hadamard { target: q0 }),
1367 ];
1368 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1369 assert_eq!(result.len(), 1);
1370 assert_eq!(result[0].name(), "Z", "H-X-H → Z");
1371 }
1372
1373 #[test]
1374 fn test_hzh_to_x() {
1375 let pass = CircuitRewriting::new(10);
1376 let q0 = q(0);
1377 let gates: Vec<Box<dyn GateOp>> = vec![
1378 Box::new(Hadamard { target: q0 }),
1379 Box::new(PauliZ { target: q0 }),
1380 Box::new(Hadamard { target: q0 }),
1381 ];
1382 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1383 assert_eq!(result.len(), 1);
1384 assert_eq!(result[0].name(), "X", "H-Z-H → X");
1385 }
1386
1387 #[test]
1388 fn test_hyh_to_y() {
1389 let pass = CircuitRewriting::new(10);
1390 let q0 = q(0);
1391 let gates: Vec<Box<dyn GateOp>> = vec![
1392 Box::new(Hadamard { target: q0 }),
1393 Box::new(PauliY { target: q0 }),
1394 Box::new(Hadamard { target: q0 }),
1395 ];
1396 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1397 assert_eq!(result.len(), 1);
1398 assert_eq!(result[0].name(), "Y", "H-Y-H → Y");
1399 }
1400
1401 #[test]
1402 fn test_hhh_converges_to_h() {
1403 let pass = CircuitRewriting::new(10);
1404 let q0 = q(0);
1405 let gates: Vec<Box<dyn GateOp>> = vec![
1406 Box::new(Hadamard { target: q0 }),
1407 Box::new(Hadamard { target: q0 }),
1408 Box::new(Hadamard { target: q0 }),
1409 ];
1410 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1411 assert_eq!(result.len(), 1, "H-H-H should converge to one H");
1412 assert_eq!(result[0].name(), "H");
1413 }
1414
1415 #[test]
1416 fn test_xxyy_cancel() {
1417 let pass = CircuitRewriting::new(10);
1418 let q0 = q(0);
1419 let gates: Vec<Box<dyn GateOp>> = vec![
1420 Box::new(PauliX { target: q0 }),
1421 Box::new(PauliX { target: q0 }),
1422 Box::new(PauliY { target: q0 }),
1423 Box::new(PauliY { target: q0 }),
1424 ];
1425 let result = pass.apply_to_gates(gates, &cost()).expect("apply failed");
1426 assert!(result.is_empty(), "X-X-Y-Y should cancel");
1427 }
1428}