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