Skip to main content

quantrs2_circuit/optimization/passes/
advanced_passes.rs

1//! Advanced optimization passes: decomposition, cost-based, and two-qubit optimization.
2
3use crate::optimization::cost_model::CostModel;
4use crate::optimization::gate_properties::get_gate_properties;
5use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
6use quantrs2_core::gate::{
7    multi,
8    single::{self},
9    GateOp,
10};
11use quantrs2_core::qubit::QubitId;
12use std::collections::{HashMap, HashSet};
13
14use super::rewriting_passes::parallelize_gates;
15use super::OptimizationPass;
16
17/// Decomposition optimization - chooses optimal decompositions based on hardware
18pub struct DecompositionOptimization {
19    target_gate_set: HashSet<String>,
20    prefer_native: bool,
21}
22
23impl DecompositionOptimization {
24    #[must_use]
25    pub const fn new(target_gate_set: HashSet<String>, prefer_native: bool) -> Self {
26        Self {
27            target_gate_set,
28            prefer_native,
29        }
30    }
31
32    #[must_use]
33    pub fn for_hardware(hardware: &str) -> Self {
34        let target_gate_set = match hardware {
35            "ibm" => vec!["X", "Y", "Z", "H", "S", "T", "RZ", "CNOT", "CZ"]
36                .into_iter()
37                .map(std::string::ToString::to_string)
38                .collect(),
39            "google" => vec!["X", "Y", "Z", "H", "RZ", "CZ", "SQRT_X"]
40                .into_iter()
41                .map(std::string::ToString::to_string)
42                .collect(),
43            _ => vec!["X", "Y", "Z", "H", "S", "T", "RZ", "RX", "RY", "CNOT"]
44                .into_iter()
45                .map(std::string::ToString::to_string)
46                .collect(),
47        };
48
49        Self {
50            target_gate_set,
51            prefer_native: true,
52        }
53    }
54}
55
56impl OptimizationPass for DecompositionOptimization {
57    fn name(&self) -> &'static str {
58        "Decomposition Optimization"
59    }
60
61    fn apply_to_gates(
62        &self,
63        gates: Vec<Box<dyn GateOp>>,
64        cost_model: &dyn CostModel,
65    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
66        let mut optimized_gates = Vec::with_capacity(gates.len() * 2);
67
68        for gate in gates {
69            let gate_name = gate.name();
70            let gate_qubits = gate.qubits();
71
72            // Check if this gate should be decomposed based on target gate set
73            if self.should_decompose(&gate, cost_model) {
74                // Decompose complex gates into simpler ones
75                match gate_name {
76                    "Toffoli" => {
77                        if gate_qubits.len() == 3 {
78                            // Decompose Toffoli into CNOT and T gates
79                            self.decompose_toffoli(&gate_qubits, &mut optimized_gates)?;
80                        } else {
81                            optimized_gates.push(gate);
82                        }
83                    }
84                    "Fredkin" | "CSWAP" => {
85                        if gate_qubits.len() == 3 {
86                            // Decompose Fredkin into CNOT gates
87                            self.decompose_fredkin(&gate_qubits, &mut optimized_gates)?;
88                        } else {
89                            optimized_gates.push(gate);
90                        }
91                    }
92                    "SWAP" => {
93                        if self.target_gate_set.contains("CNOT") && gate_qubits.len() == 2 {
94                            // Decompose SWAP into 3 CNOTs
95                            self.decompose_swap(&gate_qubits, &mut optimized_gates)?;
96                        } else {
97                            optimized_gates.push(gate);
98                        }
99                    }
100                    "CRX" | "CRY" | "CRZ" => {
101                        // Decompose controlled rotations if not in target set
102                        if !self.target_gate_set.contains(gate_name) && gate_qubits.len() == 2 {
103                            self.decompose_controlled_rotation(&gate, &mut optimized_gates)?;
104                        } else {
105                            optimized_gates.push(gate);
106                        }
107                    }
108                    _ => {
109                        // Keep gates that don't need decomposition
110                        optimized_gates.push(gate);
111                    }
112                }
113            } else {
114                optimized_gates.push(gate);
115            }
116        }
117
118        Ok(optimized_gates)
119    }
120}
121
122impl DecompositionOptimization {
123    fn should_decompose(&self, gate: &Box<dyn GateOp>, _cost_model: &dyn CostModel) -> bool {
124        let gate_name = gate.name();
125
126        // Always decompose if gate is not in target set
127        if self.target_gate_set.contains(gate_name) {
128            false
129        } else {
130            // Only decompose gates we know how to decompose
131            matches!(
132                gate_name,
133                "Toffoli" | "Fredkin" | "CSWAP" | "SWAP" | "CRX" | "CRY" | "CRZ"
134            )
135        }
136    }
137
138    fn decompose_toffoli(
139        &self,
140        qubits: &[QubitId],
141        gates: &mut Vec<Box<dyn GateOp>>,
142    ) -> QuantRS2Result<()> {
143        if qubits.len() != 3 {
144            return Err(QuantRS2Error::InvalidInput(
145                "Toffoli gate requires exactly 3 qubits".to_string(),
146            ));
147        }
148
149        let c1 = qubits[0];
150        let c2 = qubits[1];
151        let target = qubits[2];
152
153        // Standard Toffoli decomposition using CNOT and T gates
154        use quantrs2_core::gate::{
155            multi::CNOT,
156            single::{Hadamard, TDagger, T},
157        };
158
159        gates.push(Box::new(Hadamard { target }));
160        gates.push(Box::new(CNOT {
161            control: c2,
162            target,
163        }));
164        gates.push(Box::new(TDagger { target }));
165        gates.push(Box::new(CNOT {
166            control: c1,
167            target,
168        }));
169        gates.push(Box::new(T { target }));
170        gates.push(Box::new(CNOT {
171            control: c2,
172            target,
173        }));
174        gates.push(Box::new(TDagger { target }));
175        gates.push(Box::new(CNOT {
176            control: c1,
177            target,
178        }));
179        gates.push(Box::new(T { target: c2 }));
180        gates.push(Box::new(T { target }));
181        gates.push(Box::new(CNOT {
182            control: c1,
183            target: c2,
184        }));
185        gates.push(Box::new(Hadamard { target }));
186        gates.push(Box::new(T { target: c1 }));
187        gates.push(Box::new(TDagger { target: c2 }));
188        gates.push(Box::new(CNOT {
189            control: c1,
190            target: c2,
191        }));
192
193        Ok(())
194    }
195
196    fn decompose_fredkin(
197        &self,
198        qubits: &[QubitId],
199        gates: &mut Vec<Box<dyn GateOp>>,
200    ) -> QuantRS2Result<()> {
201        if qubits.len() != 3 {
202            return Err(QuantRS2Error::InvalidInput(
203                "Fredkin gate requires exactly 3 qubits".to_string(),
204            ));
205        }
206
207        let control = qubits[0];
208        let target1 = qubits[1];
209        let target2 = qubits[2];
210
211        // Fredkin decomposition using CNOT gates
212        use quantrs2_core::gate::multi::CNOT;
213
214        gates.push(Box::new(CNOT {
215            control: target2,
216            target: target1,
217        }));
218        gates.push(Box::new(CNOT {
219            control,
220            target: target1,
221        }));
222        gates.push(Box::new(CNOT {
223            control: target1,
224            target: target2,
225        }));
226        gates.push(Box::new(CNOT {
227            control,
228            target: target1,
229        }));
230        gates.push(Box::new(CNOT {
231            control: target2,
232            target: target1,
233        }));
234
235        Ok(())
236    }
237
238    fn decompose_swap(
239        &self,
240        qubits: &[QubitId],
241        gates: &mut Vec<Box<dyn GateOp>>,
242    ) -> QuantRS2Result<()> {
243        if qubits.len() != 2 {
244            return Err(QuantRS2Error::InvalidInput(
245                "SWAP gate requires exactly 2 qubits".to_string(),
246            ));
247        }
248
249        let q1 = qubits[0];
250        let q2 = qubits[1];
251
252        // SWAP decomposition using 3 CNOT gates
253        use quantrs2_core::gate::multi::CNOT;
254
255        gates.push(Box::new(CNOT {
256            control: q1,
257            target: q2,
258        }));
259        gates.push(Box::new(CNOT {
260            control: q2,
261            target: q1,
262        }));
263        gates.push(Box::new(CNOT {
264            control: q1,
265            target: q2,
266        }));
267
268        Ok(())
269    }
270
271    fn decompose_controlled_rotation(
272        &self,
273        gate: &Box<dyn GateOp>,
274        gates: &mut Vec<Box<dyn GateOp>>,
275    ) -> QuantRS2Result<()> {
276        let qubits = gate.qubits();
277        if qubits.len() != 2 {
278            return Err(QuantRS2Error::InvalidInput(
279                "Controlled rotation requires exactly 2 qubits".to_string(),
280            ));
281        }
282
283        let control = qubits[0];
284        let target = qubits[1];
285
286        use quantrs2_core::gate::{
287            multi::CNOT,
288            single::{RotationX, RotationY, RotationZ},
289        };
290
291        match gate.name() {
292            "CRX" => {
293                gates.push(Box::new(RotationX {
294                    target,
295                    theta: std::f64::consts::PI / 4.0,
296                }));
297                gates.push(Box::new(CNOT { control, target }));
298                gates.push(Box::new(RotationX {
299                    target,
300                    theta: -std::f64::consts::PI / 4.0,
301                }));
302                gates.push(Box::new(CNOT { control, target }));
303            }
304            "CRY" => {
305                gates.push(Box::new(RotationY {
306                    target,
307                    theta: std::f64::consts::PI / 4.0,
308                }));
309                gates.push(Box::new(CNOT { control, target }));
310                gates.push(Box::new(RotationY {
311                    target,
312                    theta: -std::f64::consts::PI / 4.0,
313                }));
314                gates.push(Box::new(CNOT { control, target }));
315            }
316            "CRZ" => {
317                gates.push(Box::new(RotationZ {
318                    target,
319                    theta: std::f64::consts::PI / 4.0,
320                }));
321                gates.push(Box::new(CNOT { control, target }));
322                gates.push(Box::new(RotationZ {
323                    target,
324                    theta: -std::f64::consts::PI / 4.0,
325                }));
326                gates.push(Box::new(CNOT { control, target }));
327            }
328            _ => {
329                return Err(QuantRS2Error::UnsupportedOperation(format!(
330                    "Unknown controlled rotation gate: {}",
331                    gate.name()
332                )));
333            }
334        }
335
336        Ok(())
337    }
338}
339
340/// Cost-based optimization - minimizes gate count, depth, or error
341pub struct CostBasedOptimization {
342    optimization_target: CostTarget,
343    max_iterations: usize,
344}
345
346#[derive(Debug, Clone, Copy)]
347pub enum CostTarget {
348    GateCount,
349    CircuitDepth,
350    TotalError,
351    ExecutionTime,
352    Balanced,
353}
354
355impl CostBasedOptimization {
356    #[must_use]
357    pub const fn new(target: CostTarget, max_iterations: usize) -> Self {
358        Self {
359            optimization_target: target,
360            max_iterations,
361        }
362    }
363}
364
365impl OptimizationPass for CostBasedOptimization {
366    fn name(&self) -> &'static str {
367        "Cost-Based Optimization"
368    }
369
370    fn apply_to_gates(
371        &self,
372        gates: Vec<Box<dyn GateOp>>,
373        cost_model: &dyn CostModel,
374    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
375        let mut best_gates = gates.clone();
376        let mut best_cost = self.calculate_cost(&best_gates, cost_model);
377
378        for iteration in 0..self.max_iterations {
379            let candidate_gates = self.generate_candidate_solution(&best_gates, iteration)?;
380            let candidate_cost = self.calculate_cost(&candidate_gates, cost_model);
381
382            if candidate_cost < best_cost {
383                best_gates = candidate_gates;
384                best_cost = candidate_cost;
385            }
386        }
387
388        Ok(best_gates)
389    }
390}
391
392impl CostBasedOptimization {
393    fn calculate_cost(&self, gates: &[Box<dyn GateOp>], cost_model: &dyn CostModel) -> f64 {
394        match self.optimization_target {
395            CostTarget::GateCount => gates.len() as f64,
396            CostTarget::CircuitDepth => self.calculate_depth(gates) as f64,
397            CostTarget::TotalError => self.calculate_total_error(gates),
398            CostTarget::ExecutionTime => self.calculate_execution_time(gates),
399            CostTarget::Balanced => {
400                let gate_count = gates.len() as f64;
401                let depth = self.calculate_depth(gates) as f64;
402                let error = self.calculate_total_error(gates);
403                let time = self.calculate_execution_time(gates);
404
405                (0.2 * error).mul_add(1000.0, 0.3f64.mul_add(gate_count, 0.3 * depth))
406                    + 0.2 * time / 1000.0
407            }
408        }
409    }
410
411    fn generate_candidate_solution(
412        &self,
413        gates: &[Box<dyn GateOp>],
414        iteration: usize,
415    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
416        let mut candidate = gates.to_vec();
417
418        match self.optimization_target {
419            CostTarget::GateCount => {
420                self.cancel_inverse_gates(&mut candidate);
421            }
422            CostTarget::CircuitDepth => {
423                self.parallelize_gates_impl(&mut candidate);
424            }
425            CostTarget::TotalError => {
426                self.reduce_error_gates(&candidate)?;
427            }
428            CostTarget::ExecutionTime => {
429                self.optimize_for_speed(&candidate)?;
430            }
431            CostTarget::Balanced => match iteration % 4 {
432                0 => self.cancel_inverse_gates(&mut candidate),
433                1 => self.parallelize_gates_impl(&mut candidate),
434                2 => self.reduce_error_gates(&candidate)?,
435                3 => self.optimize_for_speed(&candidate)?,
436                _ => unreachable!(),
437            },
438        }
439
440        Ok(candidate)
441    }
442
443    fn calculate_depth(&self, gates: &[Box<dyn GateOp>]) -> usize {
444        let mut qubit_depths = std::collections::HashMap::new();
445        let mut max_depth = 0;
446
447        for gate in gates {
448            let gate_qubits = gate.qubits();
449            let gate_start_depth = gate_qubits
450                .iter()
451                .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
452                .max()
453                .unwrap_or(0);
454
455            let gate_end_depth = gate_start_depth + 1;
456
457            for qubit in gate_qubits {
458                qubit_depths.insert(qubit.id(), gate_end_depth);
459            }
460
461            max_depth = max_depth.max(gate_end_depth);
462        }
463
464        max_depth
465    }
466
467    fn calculate_total_error(&self, gates: &[Box<dyn GateOp>]) -> f64 {
468        gates
469            .iter()
470            .map(|gate| self.estimate_gate_error(gate.name()))
471            .sum()
472    }
473
474    fn calculate_execution_time(&self, gates: &[Box<dyn GateOp>]) -> f64 {
475        gates
476            .iter()
477            .map(|gate| self.estimate_gate_time(gate.name()))
478            .sum()
479    }
480
481    fn estimate_gate_error(&self, gate_name: &str) -> f64 {
482        match gate_name {
483            "H" | "X" | "Y" | "Z" | "S" | "T" => 0.0001,
484            "RX" | "RY" | "RZ" => 0.0005,
485            "CNOT" | "CX" | "CZ" | "CY" => 0.01,
486            "SWAP" | "CRX" | "CRY" | "CRZ" => 0.015,
487            "Toffoli" | "Fredkin" => 0.05,
488            _ => 0.01,
489        }
490    }
491
492    fn estimate_gate_time(&self, gate_name: &str) -> f64 {
493        match gate_name {
494            "H" | "X" | "Y" | "Z" | "S" | "T" | "RX" | "RY" | "RZ" => 50.0,
495            "CNOT" | "CX" | "CZ" | "CY" | "SWAP" | "CRX" | "CRY" | "CRZ" => 200.0,
496            "Toffoli" | "Fredkin" => 500.0,
497            _ => 100.0,
498        }
499    }
500
501    fn cancel_inverse_gates(&self, gates: &mut Vec<Box<dyn GateOp>>) {
502        let mut i = 0;
503        while i + 1 < gates.len() {
504            if self.are_inverse_gates(&gates[i], &gates[i + 1]) {
505                gates.remove(i + 1);
506                gates.remove(i);
507                i = i.saturating_sub(1);
508            } else {
509                i += 1;
510            }
511        }
512    }
513
514    fn are_inverse_gates(&self, gate1: &Box<dyn GateOp>, gate2: &Box<dyn GateOp>) -> bool {
515        if gate1.qubits() != gate2.qubits() {
516            return false;
517        }
518
519        match (gate1.name(), gate2.name()) {
520            ("H", "H") | ("X", "X") | ("Y", "Y") | ("Z", "Z") => true,
521            ("S", "SDG") | ("SDG", "S") => true,
522            ("T", "TDG") | ("TDG", "T") => true,
523            ("CNOT", "CNOT") | ("CX", "CX") => true,
524            _ => false,
525        }
526    }
527
528    fn parallelize_gates_impl(&self, gates: &mut Vec<Box<dyn GateOp>>) {
529        let owned = std::mem::take(gates);
530        *gates = parallelize_gates(owned);
531    }
532
533    fn reduce_error_gates(&self, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<()> {
534        for i in 0..gates.len() {
535            if gates[i].name() == "Toffoli" {
536                // Could decompose Toffoli to reduce error in some cases
537            }
538        }
539        Ok(())
540    }
541
542    fn optimize_for_speed(&self, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<()> {
543        for i in 0..gates.len() {
544            if gates[i].name() == "Toffoli" {
545                // Could use a faster Toffoli implementation if available
546            }
547        }
548        Ok(())
549    }
550}
551
552/// Two-qubit gate optimization
553pub struct TwoQubitOptimization {
554    use_kak_decomposition: bool,
555    optimize_cnots: bool,
556}
557
558impl TwoQubitOptimization {
559    #[must_use]
560    pub const fn new(use_kak_decomposition: bool, optimize_cnots: bool) -> Self {
561        Self {
562            use_kak_decomposition,
563            optimize_cnots,
564        }
565    }
566}
567
568impl TwoQubitOptimization {
569    /// Qubit ids touched by a gate as a `HashSet<u32>`.
570    fn qubit_ids(gate: &dyn GateOp) -> HashSet<u32> {
571        gate.qubits().into_iter().map(|q| q.id()).collect()
572    }
573
574    /// True when at least one gate in `gates[from..to]` touches qubit `qid`.
575    fn range_touches(gates: &[Box<dyn GateOp>], from: usize, to: usize, qid: u32) -> bool {
576        if from >= to {
577            return false;
578        }
579        gates[from..to]
580            .iter()
581            .any(|g| g.qubits().iter().any(|q| q.id() == qid))
582    }
583
584    /// First index >= `start` (excluding entries in `skip`) that touches qa or qb.
585    fn next_on_pair(
586        gates: &[Box<dyn GateOp>],
587        start: usize,
588        skip: &[bool],
589        qa: u32,
590        qb: u32,
591    ) -> Option<usize> {
592        for k in start..gates.len() {
593            if skip[k] {
594                continue;
595            }
596            let ids = Self::qubit_ids(gates[k].as_ref());
597            if ids.contains(&qa) || ids.contains(&qb) {
598                return Some(k);
599            }
600        }
601        None
602    }
603
604    /// One optimisation sweep. Returns `(new_gates, did_change)`.
605    fn sweep(gates: Vec<Box<dyn GateOp>>) -> (Vec<Box<dyn GateOp>>, bool) {
606        let n = gates.len();
607        let mut skip = vec![false; n];
608        let mut swap_at: HashMap<usize, multi::SWAP> = HashMap::new();
609        let mut cnot_at: HashMap<usize, multi::CNOT> = HashMap::new();
610
611        // --- Rule 1: CNOT(a,b) immediately followed (on that pair) by CNOT(a,b) ---
612        for i in 0..n {
613            if skip[i] {
614                continue;
615            }
616            let ci = match gates[i].as_any().downcast_ref::<multi::CNOT>() {
617                Some(c) => *c,
618                None => continue,
619            };
620            let (qa, qb) = (ci.control.id(), ci.target.id());
621            if let Some(j) = Self::next_on_pair(&gates, i + 1, &skip, qa, qb) {
622                if let Some(cj) = gates[j].as_any().downcast_ref::<multi::CNOT>() {
623                    if cj.control.id() == qa && cj.target.id() == qb {
624                        skip[i] = true;
625                        skip[j] = true;
626                    }
627                }
628            }
629        }
630
631        // --- Rule 2: CNOT(a,b), CNOT(b,a), CNOT(a,b) → SWAP(a,b) ---
632        for i in 0..n {
633            if skip[i] {
634                continue;
635            }
636            let c0 = match gates[i].as_any().downcast_ref::<multi::CNOT>() {
637                Some(c) => *c,
638                None => continue,
639            };
640            let (qa, qb) = (c0.control.id(), c0.target.id());
641            let j1 = match Self::next_on_pair(&gates, i + 1, &skip, qa, qb) {
642                Some(k) => k,
643                None => continue,
644            };
645            match gates[j1].as_any().downcast_ref::<multi::CNOT>() {
646                Some(c) if c.control.id() == qb && c.target.id() == qa => {}
647                _ => continue,
648            }
649            let j2 = match Self::next_on_pair(&gates, j1 + 1, &skip, qa, qb) {
650                Some(k) => k,
651                None => continue,
652            };
653            if let Some(c2) = gates[j2].as_any().downcast_ref::<multi::CNOT>() {
654                if c2.control.id() == qa && c2.target.id() == qb {
655                    skip[i] = true;
656                    skip[j1] = true;
657                    skip[j2] = true;
658                    swap_at.insert(
659                        i,
660                        multi::SWAP {
661                            qubit1: c0.control,
662                            qubit2: c0.target,
663                        },
664                    );
665                }
666            }
667        }
668
669        // --- Rule 3: H(b) CZ(a,b) H(b) → CNOT(a,b) ---
670        for i in 0..n {
671            if skip[i] {
672                continue;
673            }
674            let h1 = match gates[i].as_any().downcast_ref::<single::Hadamard>() {
675                Some(h) => *h,
676                None => continue,
677            };
678            let qb = h1.target.id();
679            let j = match Self::next_on_pair(&gates, i + 1, &skip, qb, qb) {
680                Some(k) => k,
681                None => continue,
682            };
683            let cz = match gates[j].as_any().downcast_ref::<multi::CZ>() {
684                Some(c) if c.control.id() == qb || c.target.id() == qb => *c,
685                _ => continue,
686            };
687            let qa = if cz.control.id() == qb {
688                cz.target.id()
689            } else {
690                cz.control.id()
691            };
692            let k = match Self::next_on_pair(&gates, j + 1, &skip, qb, qb) {
693                Some(k) => k,
694                None => continue,
695            };
696            match gates[k].as_any().downcast_ref::<single::Hadamard>() {
697                Some(h) if h.target.id() == qb => {}
698                _ => continue,
699            }
700            if Self::range_touches(&gates, i + 1, j, qa)
701                || Self::range_touches(&gates, j + 1, k, qa)
702            {
703                continue;
704            }
705            let (ctrl, tgt) = if cz.control.id() == qa {
706                (cz.control, cz.target)
707            } else {
708                (cz.target, cz.control)
709            };
710            skip[i] = true;
711            skip[j] = true;
712            skip[k] = true;
713            cnot_at.insert(
714                i,
715                multi::CNOT {
716                    control: ctrl,
717                    target: tgt,
718                },
719            );
720        }
721
722        let any_change = skip.iter().any(|&s| s);
723        if !any_change {
724            return (gates, false);
725        }
726
727        let mut result: Vec<Box<dyn GateOp>> = Vec::with_capacity(n);
728        for idx in 0..n {
729            if !skip[idx] {
730                result.push(gates[idx].clone());
731            } else if let Some(&swap) = swap_at.get(&idx) {
732                result.push(Box::new(swap));
733            } else if let Some(&cnot) = cnot_at.get(&idx) {
734                result.push(Box::new(cnot));
735            }
736            // otherwise: gate is cancelled — emit nothing
737        }
738        (result, true)
739    }
740
741    /// Apply all two-qubit optimisations to convergence.
742    fn optimize_two_qubit(gates: Vec<Box<dyn GateOp>>) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
743        let mut work = gates;
744        loop {
745            let (next, changed) = Self::sweep(work);
746            work = next;
747            if !changed {
748                break;
749            }
750        }
751        Ok(work)
752    }
753}
754
755impl OptimizationPass for TwoQubitOptimization {
756    fn name(&self) -> &'static str {
757        "Two-Qubit Optimization"
758    }
759
760    fn apply_to_gates(
761        &self,
762        gates: Vec<Box<dyn GateOp>>,
763        _cost_model: &dyn CostModel,
764    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
765        if !self.optimize_cnots {
766            return Ok(gates);
767        }
768        Self::optimize_two_qubit(gates)
769    }
770}