Skip to main content

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                ((original_t - optimized_t) * 100)
69                    .checked_div(original_t)
70                    .unwrap_or(0)
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
140            .optimize(gates.clone())
141            .expect("Failed to optimize circuit with ZX pass");
142
143        // Should optimize the circuit
144        assert!(optimized.len() <= gates.len());
145    }
146
147    #[test]
148    fn test_zx_not_applicable() {
149        // Circuit with no supported gates
150        let gates: Vec<Box<dyn GateOp>> = vec![];
151
152        let pass = ZXOptimizationPass::new();
153        assert!(!pass.is_applicable(&gates));
154    }
155
156    #[test]
157    fn test_zx_with_optimization_chain() {
158        use crate::optimization::OptimizationChain;
159
160        let gates: Vec<Box<dyn GateOp>> = vec![
161            Box::new(RotationZ {
162                target: QubitId(0),
163                theta: std::f64::consts::PI / 4.0,
164            }),
165            Box::new(RotationZ {
166                target: QubitId(0),
167                theta: std::f64::consts::PI / 4.0,
168            }),
169            Box::new(CNOT {
170                control: QubitId(0),
171                target: QubitId(1),
172            }),
173            Box::new(CNOT {
174                control: QubitId(0),
175                target: QubitId(1),
176            }),
177        ];
178
179        let chain = OptimizationChain::new().add_pass(Box::new(ZXOptimizationPass::new()));
180
181        let optimized = chain
182            .optimize(gates.clone())
183            .expect("Failed to optimize circuit with ZX chain");
184
185        // Should optimize both the T gates and CNOT cancellation
186        assert!(optimized.len() < gates.len());
187    }
188}