Skip to main content

quantrs2_circuit/
gate_applier.rs

1//! Single-qubit, two-qubit, and three-qubit gate application methods for [`Circuit`].
2//!
3//! This module provides all the gate-application convenience methods on `Circuit<N>`,
4//! including individual gates (h, x, y, z, s, t, rx, ry, rz, cnot, …) as well as
5//! bulk helpers (`h_all`, `x_all`, `cnot_all`, …) and structural helpers
6//! (`measure`, `reset`, `barrier`, `measure_all`).
7
8use std::sync::Arc;
9
10use quantrs2_core::{
11    error::QuantRS2Result,
12    gate::{
13        multi::{
14            Fredkin, ISwap, Toffoli, CH, CNOT, CRX, CRY, CRZ, CS, CY, CZ, DCX, ECR, RXX, RYY, RZX,
15            RZZ, SWAP,
16        },
17        single::{
18            Hadamard, Identity, PGate, PauliX, PauliY, PauliZ, Phase, PhaseDagger, RotationX,
19            RotationY, RotationZ, SqrtX, SqrtXDagger, TDagger, UGate, T,
20        },
21        GateOp,
22    },
23    qubit::QubitId,
24};
25
26use crate::builder::{BarrierInfo, Circuit, Measure};
27
28impl<const N: usize> Circuit<N> {
29    // ============ Single-Qubit Gates ============
30
31    /// Apply a Hadamard gate to a qubit.
32    ///
33    /// # Examples
34    ///
35    /// ```rust
36    /// use quantrs2_circuit::builder::Circuit;
37    /// let mut circ: Circuit<2> = Circuit::new();
38    /// circ.h(0).expect("h gate failed");
39    /// assert_eq!(circ.num_gates(), 1);
40    /// ```
41    pub fn h(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
42        self.add_gate(Hadamard {
43            target: target.into(),
44        })
45    }
46
47    /// Apply a Pauli-X gate to a qubit (bit-flip / NOT gate).
48    ///
49    /// # Examples
50    ///
51    /// ```rust
52    /// use quantrs2_circuit::builder::Circuit;
53    /// let mut circ: Circuit<1> = Circuit::new();
54    /// circ.x(0).expect("x gate failed");
55    /// assert_eq!(circ.num_gates(), 1);
56    /// ```
57    pub fn x(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
58        self.add_gate(PauliX {
59            target: target.into(),
60        })
61    }
62
63    /// Apply a Pauli-Y gate to a qubit
64    pub fn y(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
65        self.add_gate(PauliY {
66            target: target.into(),
67        })
68    }
69
70    /// Apply a Pauli-Z gate to a qubit
71    pub fn z(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
72        self.add_gate(PauliZ {
73            target: target.into(),
74        })
75    }
76
77    /// Apply a rotation around X-axis
78    pub fn rx(&mut self, target: impl Into<QubitId>, theta: f64) -> QuantRS2Result<&mut Self> {
79        self.add_gate(RotationX {
80            target: target.into(),
81            theta,
82        })
83    }
84
85    /// Apply a rotation around Y-axis
86    pub fn ry(&mut self, target: impl Into<QubitId>, theta: f64) -> QuantRS2Result<&mut Self> {
87        self.add_gate(RotationY {
88            target: target.into(),
89            theta,
90        })
91    }
92
93    /// Apply a rotation around Z-axis
94    pub fn rz(&mut self, target: impl Into<QubitId>, theta: f64) -> QuantRS2Result<&mut Self> {
95        self.add_gate(RotationZ {
96            target: target.into(),
97            theta,
98        })
99    }
100
101    /// Apply a Phase gate (S gate)
102    pub fn s(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
103        self.add_gate(Phase {
104            target: target.into(),
105        })
106    }
107
108    /// Apply a Phase-dagger gate (S† gate)
109    pub fn sdg(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
110        self.add_gate(PhaseDagger {
111            target: target.into(),
112        })
113    }
114
115    /// Apply a T gate
116    pub fn t(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
117        self.add_gate(T {
118            target: target.into(),
119        })
120    }
121
122    /// Apply a T-dagger gate (T† gate)
123    pub fn tdg(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
124        self.add_gate(TDagger {
125            target: target.into(),
126        })
127    }
128
129    /// Apply a Square Root of X gate (√X)
130    pub fn sx(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
131        self.add_gate(SqrtX {
132            target: target.into(),
133        })
134    }
135
136    /// Apply a Square Root of X Dagger gate (√X†)
137    pub fn sxdg(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
138        self.add_gate(SqrtXDagger {
139            target: target.into(),
140        })
141    }
142
143    // ============ Qiskit-Compatible Single-Qubit Gates ============
144
145    /// Apply a U gate (general single-qubit rotation)
146    ///
147    /// U(θ, φ, λ) = [[cos(θ/2), -e^(iλ)·sin(θ/2)],
148    ///              [e^(iφ)·sin(θ/2), e^(i(φ+λ))·cos(θ/2)]]
149    pub fn u(
150        &mut self,
151        target: impl Into<QubitId>,
152        theta: f64,
153        phi: f64,
154        lambda: f64,
155    ) -> QuantRS2Result<&mut Self> {
156        self.add_gate(UGate {
157            target: target.into(),
158            theta,
159            phi,
160            lambda,
161        })
162    }
163
164    /// Apply a P gate (phase gate with parameter)
165    ///
166    /// P(λ) = [[1, 0], [0, e^(iλ)]]
167    pub fn p(&mut self, target: impl Into<QubitId>, lambda: f64) -> QuantRS2Result<&mut Self> {
168        self.add_gate(PGate {
169            target: target.into(),
170            lambda,
171        })
172    }
173
174    /// Apply an Identity gate
175    pub fn id(&mut self, target: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
176        self.add_gate(Identity {
177            target: target.into(),
178        })
179    }
180
181    // ============ Two-Qubit Gates ============
182
183    /// Apply a CNOT gate (controlled-X).
184    ///
185    /// Flips `target` when `control` is |1⟩.
186    ///
187    /// # Examples
188    ///
189    /// ```rust
190    /// use quantrs2_circuit::builder::Circuit;
191    /// let mut circ: Circuit<2> = Circuit::new();
192    /// circ.h(0).expect("h failed").cnot(0, 1).expect("cnot failed");
193    /// assert_eq!(circ.num_gates(), 2);
194    /// ```
195    pub fn cnot(
196        &mut self,
197        control: impl Into<QubitId>,
198        target: impl Into<QubitId>,
199    ) -> QuantRS2Result<&mut Self> {
200        self.add_gate(CNOT {
201            control: control.into(),
202            target: target.into(),
203        })
204    }
205
206    /// Apply a CNOT gate (alias for cnot)
207    pub fn cx(
208        &mut self,
209        control: impl Into<QubitId>,
210        target: impl Into<QubitId>,
211    ) -> QuantRS2Result<&mut Self> {
212        self.cnot(control, target)
213    }
214
215    /// Apply a CY gate (Controlled-Y)
216    pub fn cy(
217        &mut self,
218        control: impl Into<QubitId>,
219        target: impl Into<QubitId>,
220    ) -> QuantRS2Result<&mut Self> {
221        self.add_gate(CY {
222            control: control.into(),
223            target: target.into(),
224        })
225    }
226
227    /// Apply a CZ gate (Controlled-Z)
228    pub fn cz(
229        &mut self,
230        control: impl Into<QubitId>,
231        target: impl Into<QubitId>,
232    ) -> QuantRS2Result<&mut Self> {
233        self.add_gate(CZ {
234            control: control.into(),
235            target: target.into(),
236        })
237    }
238
239    /// Apply a CH gate (Controlled-Hadamard)
240    pub fn ch(
241        &mut self,
242        control: impl Into<QubitId>,
243        target: impl Into<QubitId>,
244    ) -> QuantRS2Result<&mut Self> {
245        self.add_gate(CH {
246            control: control.into(),
247            target: target.into(),
248        })
249    }
250
251    /// Apply a CS gate (Controlled-Phase/S)
252    pub fn cs(
253        &mut self,
254        control: impl Into<QubitId>,
255        target: impl Into<QubitId>,
256    ) -> QuantRS2Result<&mut Self> {
257        self.add_gate(CS {
258            control: control.into(),
259            target: target.into(),
260        })
261    }
262
263    /// Apply a controlled rotation around X-axis (CRX)
264    pub fn crx(
265        &mut self,
266        control: impl Into<QubitId>,
267        target: impl Into<QubitId>,
268        theta: f64,
269    ) -> QuantRS2Result<&mut Self> {
270        self.add_gate(CRX {
271            control: control.into(),
272            target: target.into(),
273            theta,
274        })
275    }
276
277    /// Apply a controlled rotation around Y-axis (CRY)
278    pub fn cry(
279        &mut self,
280        control: impl Into<QubitId>,
281        target: impl Into<QubitId>,
282        theta: f64,
283    ) -> QuantRS2Result<&mut Self> {
284        self.add_gate(CRY {
285            control: control.into(),
286            target: target.into(),
287            theta,
288        })
289    }
290
291    /// Apply a controlled rotation around Z-axis (CRZ)
292    pub fn crz(
293        &mut self,
294        control: impl Into<QubitId>,
295        target: impl Into<QubitId>,
296        theta: f64,
297    ) -> QuantRS2Result<&mut Self> {
298        self.add_gate(CRZ {
299            control: control.into(),
300            target: target.into(),
301            theta,
302        })
303    }
304
305    /// Apply a controlled phase gate
306    pub fn cp(
307        &mut self,
308        control: impl Into<QubitId>,
309        target: impl Into<QubitId>,
310        lambda: f64,
311    ) -> QuantRS2Result<&mut Self> {
312        // CRZ(lambda) is equivalent to CP(lambda) up to a global phase
313        self.crz(control, target, lambda)
314    }
315
316    /// Apply a SWAP gate
317    pub fn swap(
318        &mut self,
319        qubit1: impl Into<QubitId>,
320        qubit2: impl Into<QubitId>,
321    ) -> QuantRS2Result<&mut Self> {
322        self.add_gate(SWAP {
323            qubit1: qubit1.into(),
324            qubit2: qubit2.into(),
325        })
326    }
327
328    /// Apply an iSWAP gate
329    ///
330    /// iSWAP swaps two qubits and phases |01⟩ and |10⟩ by i
331    pub fn iswap(
332        &mut self,
333        qubit1: impl Into<QubitId>,
334        qubit2: impl Into<QubitId>,
335    ) -> QuantRS2Result<&mut Self> {
336        self.add_gate(ISwap {
337            qubit1: qubit1.into(),
338            qubit2: qubit2.into(),
339        })
340    }
341
342    /// Apply an ECR gate (IBM native echoed cross-resonance gate)
343    pub fn ecr(
344        &mut self,
345        control: impl Into<QubitId>,
346        target: impl Into<QubitId>,
347    ) -> QuantRS2Result<&mut Self> {
348        self.add_gate(ECR {
349            control: control.into(),
350            target: target.into(),
351        })
352    }
353
354    /// Apply an RXX gate (two-qubit XX rotation)
355    ///
356    /// RXX(θ) = exp(-i * θ/2 * X⊗X)
357    pub fn rxx(
358        &mut self,
359        qubit1: impl Into<QubitId>,
360        qubit2: impl Into<QubitId>,
361        theta: f64,
362    ) -> QuantRS2Result<&mut Self> {
363        self.add_gate(RXX {
364            qubit1: qubit1.into(),
365            qubit2: qubit2.into(),
366            theta,
367        })
368    }
369
370    /// Apply an RYY gate (two-qubit YY rotation)
371    ///
372    /// RYY(θ) = exp(-i * θ/2 * Y⊗Y)
373    pub fn ryy(
374        &mut self,
375        qubit1: impl Into<QubitId>,
376        qubit2: impl Into<QubitId>,
377        theta: f64,
378    ) -> QuantRS2Result<&mut Self> {
379        self.add_gate(RYY {
380            qubit1: qubit1.into(),
381            qubit2: qubit2.into(),
382            theta,
383        })
384    }
385
386    /// Apply an RZZ gate (two-qubit ZZ rotation)
387    ///
388    /// RZZ(θ) = exp(-i * θ/2 * Z⊗Z)
389    pub fn rzz(
390        &mut self,
391        qubit1: impl Into<QubitId>,
392        qubit2: impl Into<QubitId>,
393        theta: f64,
394    ) -> QuantRS2Result<&mut Self> {
395        self.add_gate(RZZ {
396            qubit1: qubit1.into(),
397            qubit2: qubit2.into(),
398            theta,
399        })
400    }
401
402    /// Apply an RZX gate (two-qubit ZX rotation / cross-resonance)
403    ///
404    /// RZX(θ) = exp(-i * θ/2 * Z⊗X)
405    pub fn rzx(
406        &mut self,
407        control: impl Into<QubitId>,
408        target: impl Into<QubitId>,
409        theta: f64,
410    ) -> QuantRS2Result<&mut Self> {
411        self.add_gate(RZX {
412            control: control.into(),
413            target: target.into(),
414            theta,
415        })
416    }
417
418    /// Apply a DCX gate (double CNOT gate)
419    ///
420    /// DCX = CNOT(0,1) @ CNOT(1,0)
421    pub fn dcx(
422        &mut self,
423        qubit1: impl Into<QubitId>,
424        qubit2: impl Into<QubitId>,
425    ) -> QuantRS2Result<&mut Self> {
426        self.add_gate(DCX {
427            qubit1: qubit1.into(),
428            qubit2: qubit2.into(),
429        })
430    }
431
432    // ============ Three-Qubit Gates ============
433
434    /// Apply a Toffoli (CCNOT) gate
435    pub fn toffoli(
436        &mut self,
437        control1: impl Into<QubitId>,
438        control2: impl Into<QubitId>,
439        target: impl Into<QubitId>,
440    ) -> QuantRS2Result<&mut Self> {
441        self.add_gate(Toffoli {
442            control1: control1.into(),
443            control2: control2.into(),
444            target: target.into(),
445        })
446    }
447
448    /// Apply a Fredkin (CSWAP) gate
449    pub fn cswap(
450        &mut self,
451        control: impl Into<QubitId>,
452        target1: impl Into<QubitId>,
453        target2: impl Into<QubitId>,
454    ) -> QuantRS2Result<&mut Self> {
455        self.add_gate(Fredkin {
456            control: control.into(),
457            target1: target1.into(),
458            target2: target2.into(),
459        })
460    }
461
462    /// Apply a CCX gate (alias for Toffoli)
463    pub fn ccx(
464        &mut self,
465        control1: impl Into<QubitId>,
466        control2: impl Into<QubitId>,
467        target: impl Into<QubitId>,
468    ) -> QuantRS2Result<&mut Self> {
469        self.toffoli(control1, control2, target)
470    }
471
472    /// Apply a Fredkin gate (alias for cswap)
473    pub fn fredkin(
474        &mut self,
475        control: impl Into<QubitId>,
476        target1: impl Into<QubitId>,
477        target2: impl Into<QubitId>,
478    ) -> QuantRS2Result<&mut Self> {
479        self.cswap(control, target1, target2)
480    }
481
482    // ============ Measurement and Control ============
483
484    /// Measure a qubit (currently adds a placeholder measure gate)
485    ///
486    /// Note: This is currently a placeholder implementation for QASM export compatibility.
487    /// For actual quantum measurements, use the measurement module functionality.
488    pub fn measure(&mut self, qubit: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
489        let qubit_id = qubit.into();
490        self.add_gate(Measure { target: qubit_id })?;
491        Ok(self)
492    }
493
494    /// Reset a qubit to |0⟩ state
495    ///
496    /// Note: This operation is not yet fully implemented.
497    /// Reset operations are complex and require special handling in quantum circuits.
498    pub fn reset(&mut self, _qubit: impl Into<QubitId>) -> QuantRS2Result<&mut Self> {
499        Err(quantrs2_core::error::QuantRS2Error::UnsupportedOperation(
500            "Reset operation is not yet implemented. Reset requires special quantum state manipulation.".to_string()
501        ))
502    }
503
504    /// Add a barrier to prevent optimization across this point
505    ///
506    /// Barriers are used to prevent gate optimization algorithms from reordering gates
507    /// across specific points in the circuit. This is useful for maintaining timing
508    /// constraints or preserving specific circuit structure.
509    pub fn barrier(&mut self, qubits: &[QubitId]) -> QuantRS2Result<&mut Self> {
510        // Validate all qubits are within range
511        for &qubit in qubits {
512            if qubit.id() as usize >= N {
513                return Err(quantrs2_core::error::QuantRS2Error::InvalidQubitId(
514                    qubit.id(),
515                ));
516            }
517        }
518
519        // Record the barrier so that optimization passes (e.g. gate commutation
520        // reordering, peephole fusion) know they must not move any gate in
521        // `qubits` across this boundary.
522        //
523        // `after_gate_index` is the current gate-list length: the barrier is
524        // logically placed *between* `gates[len-1]` and the next gate appended
525        // after this call.
526        self.barriers.push(BarrierInfo {
527            after_gate_index: self.gates().len(),
528            qubits: qubits.to_vec(),
529        });
530        Ok(self)
531    }
532
533    /// Measure all qubits in the circuit
534    pub fn measure_all(&mut self) -> QuantRS2Result<&mut Self> {
535        for i in 0..N {
536            self.measure(QubitId(i as u32))?;
537        }
538        Ok(self)
539    }
540
541    // ============ Batch / Bulk Gate Helpers ============
542
543    /// Apply Hadamard gates to multiple qubits at once
544    ///
545    /// # Example
546    /// ```ignore
547    /// let mut circuit = Circuit::<5>::new();
548    /// circuit.h_all(&[0, 1, 2])?; // Apply H to qubits 0, 1, and 2
549    /// ```
550    pub fn h_all(&mut self, qubits: &[u32]) -> QuantRS2Result<&mut Self> {
551        for &qubit in qubits {
552            self.h(QubitId::new(qubit))?;
553        }
554        Ok(self)
555    }
556
557    /// Apply Pauli-X gates to multiple qubits at once
558    ///
559    /// # Example
560    /// ```ignore
561    /// let mut circuit = Circuit::<5>::new();
562    /// circuit.x_all(&[0, 2, 4])?; // Apply X to qubits 0, 2, and 4
563    /// ```
564    pub fn x_all(&mut self, qubits: &[u32]) -> QuantRS2Result<&mut Self> {
565        for &qubit in qubits {
566            self.x(QubitId::new(qubit))?;
567        }
568        Ok(self)
569    }
570
571    /// Apply Pauli-Y gates to multiple qubits at once
572    pub fn y_all(&mut self, qubits: &[u32]) -> QuantRS2Result<&mut Self> {
573        for &qubit in qubits {
574            self.y(QubitId::new(qubit))?;
575        }
576        Ok(self)
577    }
578
579    /// Apply Pauli-Z gates to multiple qubits at once
580    pub fn z_all(&mut self, qubits: &[u32]) -> QuantRS2Result<&mut Self> {
581        for &qubit in qubits {
582            self.z(QubitId::new(qubit))?;
583        }
584        Ok(self)
585    }
586
587    /// Apply Hadamard gates to a range of qubits
588    ///
589    /// # Example
590    /// ```ignore
591    /// let mut circuit = Circuit::<5>::new();
592    /// circuit.h_range(0..3)?; // Apply H to qubits 0, 1, and 2
593    /// ```
594    pub fn h_range(&mut self, range: std::ops::Range<u32>) -> QuantRS2Result<&mut Self> {
595        for qubit in range {
596            self.h(QubitId::new(qubit))?;
597        }
598        Ok(self)
599    }
600
601    /// Apply Pauli-X gates to a range of qubits
602    pub fn x_range(&mut self, range: std::ops::Range<u32>) -> QuantRS2Result<&mut Self> {
603        for qubit in range {
604            self.x(QubitId::new(qubit))?;
605        }
606        Ok(self)
607    }
608
609    /// Apply a rotation gate to multiple qubits with the same angle
610    pub fn rx_all(&mut self, qubits: &[u32], theta: f64) -> QuantRS2Result<&mut Self> {
611        for &qubit in qubits {
612            self.rx(QubitId::new(qubit), theta)?;
613        }
614        Ok(self)
615    }
616
617    /// Apply RY rotation to multiple qubits
618    pub fn ry_all(&mut self, qubits: &[u32], theta: f64) -> QuantRS2Result<&mut Self> {
619        for &qubit in qubits {
620            self.ry(QubitId::new(qubit), theta)?;
621        }
622        Ok(self)
623    }
624
625    /// Apply RZ rotation to multiple qubits
626    pub fn rz_all(&mut self, qubits: &[u32], theta: f64) -> QuantRS2Result<&mut Self> {
627        for &qubit in qubits {
628            self.rz(QubitId::new(qubit), theta)?;
629        }
630        Ok(self)
631    }
632
633    /// Apply SWAP gates to multiple qubit pairs
634    ///
635    /// # Arguments
636    /// * `pairs` - Slice of (control, target) qubit pairs
637    ///
638    /// # Example
639    /// ```ignore
640    /// let mut circuit = Circuit::<6>::new();
641    /// circuit.swap_all(&[(0, 1), (2, 3), (4, 5)])?; // Swap three pairs simultaneously
642    /// ```
643    pub fn swap_all(&mut self, pairs: &[(u32, u32)]) -> QuantRS2Result<&mut Self> {
644        for &(q1, q2) in pairs {
645            self.swap(QubitId::new(q1), QubitId::new(q2))?;
646        }
647        Ok(self)
648    }
649
650    /// Apply CZ gates to multiple qubit pairs
651    ///
652    /// # Arguments
653    /// * `pairs` - Slice of (control, target) qubit pairs
654    ///
655    /// # Example
656    /// ```ignore
657    /// let mut circuit = Circuit::<6>::new();
658    /// circuit.cz_all(&[(0, 1), (2, 3), (4, 5)])?; // Apply CZ to three pairs
659    /// ```
660    pub fn cz_all(&mut self, pairs: &[(u32, u32)]) -> QuantRS2Result<&mut Self> {
661        for &(q1, q2) in pairs {
662            self.cz(QubitId::new(q1), QubitId::new(q2))?;
663        }
664        Ok(self)
665    }
666
667    /// Apply CNOT gates to multiple qubit pairs
668    ///
669    /// # Arguments
670    /// * `pairs` - Slice of (control, target) qubit pairs
671    ///
672    /// # Example
673    /// ```ignore
674    /// let mut circuit = Circuit::<6>::new();
675    /// circuit.cnot_all(&[(0, 1), (2, 3), (4, 5)])?; // Apply CNOT to three pairs
676    /// ```
677    pub fn cnot_all(&mut self, pairs: &[(u32, u32)]) -> QuantRS2Result<&mut Self> {
678        for &(control, target) in pairs {
679            self.cnot(QubitId::new(control), QubitId::new(target))?;
680        }
681        Ok(self)
682    }
683
684    /// Add barriers to multiple qubits
685    ///
686    /// Barriers prevent optimization across them and can be used to
687    /// visualize circuit structure.
688    ///
689    /// # Example
690    /// ```ignore
691    /// let mut circuit = Circuit::<5>::new();
692    /// circuit.h_all(&[0, 1, 2])?;
693    /// circuit.barrier_all(&[0, 1, 2])?; // Prevent optimization across this point
694    /// circuit.cnot_ladder(&[0, 1, 2])?;
695    /// ```
696    pub fn barrier_all(&mut self, qubits: &[u32]) -> QuantRS2Result<&mut Self> {
697        let qubit_ids: Vec<QubitId> = qubits.iter().map(|&q| QubitId::new(q)).collect();
698        self.barrier(&qubit_ids)?;
699        Ok(self)
700    }
701
702    // ============ Python Feature-Gated Helpers ============
703
704    /// Get a qubit for a specific single-qubit gate by gate type and index
705    #[cfg(feature = "python")]
706    pub fn get_single_qubit_for_gate(&self, gate_type: &str, index: usize) -> pyo3::PyResult<u32> {
707        self.find_gate_by_type_and_index(gate_type, index)
708            .and_then(|gate| {
709                if gate.qubits().len() == 1 {
710                    Some(gate.qubits()[0].id())
711                } else {
712                    None
713                }
714            })
715            .ok_or_else(|| {
716                pyo3::exceptions::PyValueError::new_err(format!(
717                    "Gate {gate_type} at index {index} not found or is not a single-qubit gate"
718                ))
719            })
720    }
721
722    /// Get rotation parameters (qubit, angle) for a specific gate by gate type and index
723    #[cfg(feature = "python")]
724    pub fn get_rotation_params_for_gate(
725        &self,
726        gate_type: &str,
727        index: usize,
728    ) -> pyo3::PyResult<(u32, f64)> {
729        self.find_gate_by_type_and_index(gate_type, index)
730            .and_then(|gate| {
731                if gate.qubits().len() == 1 {
732                    Some((gate.qubits()[0].id(), 0.0))
733                } else {
734                    None
735                }
736            })
737            .ok_or_else(|| {
738                pyo3::exceptions::PyValueError::new_err(format!(
739                    "Gate {gate_type} at index {index} not found or is not a rotation gate"
740                ))
741            })
742    }
743
744    /// Get two-qubit parameters (control, target) for a specific gate by gate type and index
745    #[cfg(feature = "python")]
746    pub fn get_two_qubit_params_for_gate(
747        &self,
748        gate_type: &str,
749        index: usize,
750    ) -> pyo3::PyResult<(u32, u32)> {
751        self.find_gate_by_type_and_index(gate_type, index)
752            .and_then(|gate| {
753                if gate.qubits().len() == 2 {
754                    Some((gate.qubits()[0].id(), gate.qubits()[1].id()))
755                } else {
756                    None
757                }
758            })
759            .ok_or_else(|| {
760                pyo3::exceptions::PyValueError::new_err(format!(
761                    "Gate {gate_type} at index {index} not found or is not a two-qubit gate"
762                ))
763            })
764    }
765
766    /// Get controlled rotation parameters (control, target, angle) for a specific gate
767    #[cfg(feature = "python")]
768    pub fn get_controlled_rotation_params_for_gate(
769        &self,
770        gate_type: &str,
771        index: usize,
772    ) -> pyo3::PyResult<(u32, u32, f64)> {
773        self.find_gate_by_type_and_index(gate_type, index)
774            .and_then(|gate| {
775                if gate.qubits().len() == 2 {
776                    Some((gate.qubits()[0].id(), gate.qubits()[1].id(), 0.0))
777                } else {
778                    None
779                }
780            })
781            .ok_or_else(|| {
782                pyo3::exceptions::PyValueError::new_err(format!(
783                    "Gate {gate_type} at index {index} not found or is not a controlled rotation gate"
784                ))
785            })
786    }
787
788    /// Get three-qubit parameters for gates like Toffoli or Fredkin
789    #[cfg(feature = "python")]
790    pub fn get_three_qubit_params_for_gate(
791        &self,
792        gate_type: &str,
793        index: usize,
794    ) -> pyo3::PyResult<(u32, u32, u32)> {
795        self.find_gate_by_type_and_index(gate_type, index)
796            .and_then(|gate| {
797                if gate.qubits().len() == 3 {
798                    Some((
799                        gate.qubits()[0].id(),
800                        gate.qubits()[1].id(),
801                        gate.qubits()[2].id(),
802                    ))
803                } else {
804                    None
805                }
806            })
807            .ok_or_else(|| {
808                pyo3::exceptions::PyValueError::new_err(format!(
809                    "Gate {gate_type} at index {index} not found or is not a three-qubit gate"
810                ))
811            })
812    }
813}