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