quantrs2_core/optimization/
zx_optimizer.rs

1//! ZX-calculus based optimization pass
2//!
3//! This module integrates ZX-calculus optimization into the
4//! general optimization framework.
5
6use crate::{
7    error::QuantRS2Result, gate::GateOp, optimization::OptimizationPass, zx_extraction::ZXPipeline,
8};
9
10/// ZX-calculus based optimization pass
11#[derive(Debug, Clone)]
12pub struct ZXOptimizationPass {
13    /// Name of this optimization pass
14    name: String,
15    /// Whether to print optimization statistics
16    verbose: bool,
17}
18
19impl Default for ZXOptimizationPass {
20    fn default() -> Self {
21        Self {
22            name: "ZX-Calculus Optimization".to_string(),
23            verbose: false,
24        }
25    }
26}
27
28impl ZXOptimizationPass {
29    /// Create a new ZX optimization pass
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Enable verbose output
35    pub fn with_verbose(mut self, verbose: bool) -> Self {
36        self.verbose = verbose;
37        self
38    }
39}
40
41impl OptimizationPass for ZXOptimizationPass {
42    fn optimize(&self, gates: Vec<Box<dyn GateOp>>) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
43        if gates.is_empty() {
44            return Ok(gates);
45        }
46
47        let pipeline = ZXPipeline::new();
48
49        if self.verbose {
50            let (original_t, _) = pipeline.compare_t_count(&gates, &gates);
51            println!(
52                "ZX-Calculus: Processing circuit with {} gates, {} T-gates",
53                gates.len(),
54                original_t
55            );
56        }
57
58        let optimized = pipeline.optimize(&gates)?;
59
60        if self.verbose {
61            let (original_t, optimized_t) = pipeline.compare_t_count(&gates, &optimized);
62            println!(
63                "ZX-Calculus: Optimized to {} gates, {} T-gates ({}% reduction)",
64                optimized.len(),
65                optimized_t,
66                if original_t > 0 {
67                    ((original_t - optimized_t) * 100) / original_t
68                } else {
69                    0
70                }
71            );
72        }
73
74        Ok(optimized)
75    }
76
77    fn name(&self) -> &str {
78        &self.name
79    }
80
81    fn is_applicable(&self, gates: &[Box<dyn GateOp>]) -> bool {
82        // ZX-calculus is particularly effective for circuits with:
83        // 1. Many single-qubit rotations
84        // 2. Clifford+T decompositions
85        // 3. CNOT and CZ gates
86
87        if gates.is_empty() {
88            return false;
89        }
90
91        // Check if circuit has supported gates
92        let has_supported_gates = gates.iter().any(|g| {
93            matches!(
94                g.name(),
95                "H" | "X" | "Y" | "Z" | "S" | "T" | "RX" | "RY" | "RZ" | "CNOT" | "CZ"
96            )
97        });
98
99        // Check if circuit would benefit from ZX optimization
100        let rotation_count = gates
101            .iter()
102            .filter(|g| matches!(g.name(), "RX" | "RY" | "RZ" | "T"))
103            .count();
104
105        let cnot_count = gates
106            .iter()
107            .filter(|g| matches!(g.name(), "CNOT" | "CZ"))
108            .count();
109
110        // Apply if there are rotations or CNOTs to optimize
111        has_supported_gates && (rotation_count > 1 || cnot_count > 1)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::gate::{multi::*, single::*};
119    use crate::qubit::QubitId;
120
121    #[test]
122    fn test_zx_optimization_pass() {
123        let gates: Vec<Box<dyn GateOp>> = vec![
124            Box::new(Hadamard { target: QubitId(0) }),
125            Box::new(RotationZ {
126                target: QubitId(0),
127                theta: std::f64::consts::PI / 4.0,
128            }),
129            Box::new(RotationZ {
130                target: QubitId(0),
131                theta: std::f64::consts::PI / 4.0,
132            }),
133            Box::new(Hadamard { target: QubitId(0) }),
134        ];
135
136        let pass = ZXOptimizationPass::new();
137        assert!(pass.is_applicable(&gates));
138
139        let optimized = pass.optimize(gates.clone()).unwrap();
140
141        // Should optimize the circuit
142        assert!(optimized.len() <= gates.len());
143    }
144
145    #[test]
146    fn test_zx_not_applicable() {
147        // Circuit with no supported gates
148        let gates: Vec<Box<dyn GateOp>> = vec![];
149
150        let pass = ZXOptimizationPass::new();
151        assert!(!pass.is_applicable(&gates));
152    }
153
154    #[test]
155    fn test_zx_with_optimization_chain() {
156        use crate::optimization::OptimizationChain;
157
158        let gates: Vec<Box<dyn GateOp>> = vec![
159            Box::new(RotationZ {
160                target: QubitId(0),
161                theta: std::f64::consts::PI / 4.0,
162            }),
163            Box::new(RotationZ {
164                target: QubitId(0),
165                theta: std::f64::consts::PI / 4.0,
166            }),
167            Box::new(CNOT {
168                control: QubitId(0),
169                target: QubitId(1),
170            }),
171            Box::new(CNOT {
172                control: QubitId(0),
173                target: QubitId(1),
174            }),
175        ];
176
177        let chain = OptimizationChain::new().add_pass(Box::new(ZXOptimizationPass::new()));
178
179        let optimized = chain.optimize(gates.clone()).unwrap();
180
181        // Should optimize both the T gates and CNOT cancellation
182        assert!(optimized.len() < gates.len());
183    }
184}