Skip to main content

cqlib_core/circuit/gate/
standard_gate.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Standard Quantum Gate Definitions
14//!
15//! This module defines the `StandardGate` enum, which enumerates all quantum logic gates
16//! natively supported by the Cqlib core. It acts as the "Instruction Set Architecture" (ISA)
17//! for the quantum virtual machine.
18//!
19//! # Key Components
20//!
21//! - [`StandardGate`]: The central enum representing gates like `H`, `CX`, `RX`, etc.
22//! - Gate Properties: Metadata such as qubit count, parameter count, and matrix representations.
23
24use crate::circuit::gate::gate_matrix;
25use crate::circuit::{CircuitError, Parameter};
26use ndarray::prelude::*;
27use num::complex::Complex;
28use smallvec::{SmallVec, smallvec};
29use std::borrow::Cow;
30use std::f64::consts::PI;
31use std::fmt;
32use std::hash::Hash;
33
34/// Represents the set of standard quantum logic gates supported natively by Cqlib.
35///
36/// This enum serves as the fundamental identifier for quantum operations that have
37/// a defined behavior and matrix representation within the core library. It includes:
38///
39/// - **Pauli Gates**: $I, X, Y, Z$
40/// - **Clifford Gates**: $H, S, T, S^\dagger, T^\dagger$
41/// - **Parametric Rotations**: $RX(\theta), RY(\theta), RZ(\theta), U(\theta, \phi, \lambda)$
42/// - **Two-Qubit Gates**: $CX, CZ, SWAP, FSim$
43/// - **Multi-Controlled Gates**: $CCX$ (Toffoli)
44///
45/// # Design Note
46/// `StandardGate` is designed to be lightweight (`Copy`, `repr(u8)`) for efficient
47/// storage and transmission.
48#[repr(u8)]
49#[derive(Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default, Clone, Copy)]
50pub enum StandardGate {
51    /// Identity gate (No-operation).
52    #[default]
53    I = 0,
54    /// Hadamard gate. Superposition creator.
55    H = 1,
56
57    // --- Single Qubit Rotations ---
58    /// Rotation around X-axis: $R_x(\theta) = e^{-i\theta X/2}$.
59    RX,
60    /// Ising XX interaction gate (two-qubit parametric).
61    RXX,
62    /// Rotation around an axis in the X-Y plane.
63    RXY,
64    /// Rotation around Y-axis: $R_y(\theta) = e^{-i\theta Y/2}$.
65    RY,
66    /// Ising YY interaction gate.
67    RYY,
68    /// Rotation around Z-axis: $R_z(\theta) = e^{-i\theta Z/2}$.
69    RZ,
70    /// Ising ZX interaction gate.
71    RZX,
72    /// Ising ZZ interaction gate.
73    RZZ,
74
75    // --- Clifford & Phase Gates ---
76    /// S gate (Phase gate, $\sqrt{Z}$).
77    S,
78    /// S-dagger gate ($S^\dagger$).
79    SDG,
80    /// SWAP gate (exchanges states of two qubits).
81    SWAP,
82    /// T gate ($\\sqrt{S}$).
83    T,
84    /// T-dagger gate ($T^\dagger$).
85    TDG,
86    /// Generic single-qubit unitary $U(\theta, \phi, \lambda)$.
87    U,
88
89    // --- Pauli Gates ---
90    /// Pauli-X gate (Bit-flip, NOT).
91    X,
92    /// Single-qubit pi rotation about an axis in the XY plane.
93    XY,
94    /// $\\sqrt{X}$ gate (SX).
95    X2P,
96    /// $\\sqrt{X}^\dagger$ gate (SXdg).
97    X2M,
98    /// Positive half-pi single-qubit rotation about an axis in the XY plane.
99    XY2P,
100    /// Negative half-pi single-qubit rotation about an axis in the XY plane.
101    XY2M,
102    /// Pauli-Y gate (Bit-phase-flip).
103    Y,
104    /// $\\sqrt{Y}$ gate.
105    Y2P,
106    /// $\\sqrt{Y}^\dagger$ gate.
107    Y2M,
108    /// Pauli-Z gate (Phase-flip).
109    Z,
110
111    // --- Global/Relative Phase ---
112    /// Phase shift gate $P(\lambda)$.
113    Phase,
114    /// Zero-qubit global phase marker.
115    ///
116    /// `GPhase` is not applied to any qubit. Whole-circuit global phase should
117    /// normally be represented by [`crate::circuit::Circuit::global_phase`];
118    /// `GPhase` operations are mainly useful as import or intermediate IR
119    /// markers, including body-local phase inside control-flow operations.
120    GPhase,
121
122    // --- Controlled Gates ---
123    /// Controlled-X gate (CNOT).
124    CX,
125    /// Toffoli gate (Controlled-Controlled-X).
126    CCX,
127    /// Controlled-Y gate.
128    CY,
129    /// Controlled-Z gate.
130    CZ,
131    /// Controlled-RX rotation.
132    CRX,
133    /// Controlled-RY rotation.
134    CRY,
135    /// Controlled-RZ rotation.
136    CRZ,
137
138    // --- Simulation Gates ---
139    /// Fermionic Simulation gate (fSim).
140    FSIM,
141}
142
143// Gate metadata lookup table.
144//
145// Maps each StandardGate variant to
146// (num_controls, num_targets, num_params). The table index must exactly match
147// the StandardGate discriminant values; do not reorder entries without
148// updating the enum definition.
149const GATE_INFO_TABLE: [(u8, u8, u8); 36] = [
150    (0, 1, 0), // I
151    (0, 1, 0), // H
152    (0, 1, 1), // RX
153    (0, 2, 1), // RXX
154    (0, 1, 2), // RXY
155    (0, 1, 1), // RY
156    (0, 2, 1), // RYY
157    (0, 1, 1), // RZ
158    (0, 2, 1), // RZX
159    (0, 2, 1), // RZZ
160    (0, 1, 0), // S
161    (0, 1, 0), // SDG
162    (0, 2, 0), // SWAP
163    (0, 1, 0), // T
164    (0, 1, 0), // TDG
165    (0, 1, 3), // U
166    (0, 1, 0), // X
167    (0, 1, 1), // XY
168    (0, 1, 0), // X2P
169    (0, 1, 0), // X2M
170    (0, 1, 1), // XY2P
171    (0, 1, 1), // XY2M
172    (0, 1, 0), // Y
173    (0, 1, 0), // Y2P
174    (0, 1, 0), // Y2M
175    (0, 1, 0), // Z
176    (0, 1, 1), // Phase
177    (0, 0, 1), // GPhase
178    (1, 1, 0), // CX
179    (2, 1, 0), // CCX
180    (1, 1, 0), // CY
181    (1, 1, 0), // CZ
182    (1, 1, 1), // CRX
183    (1, 1, 1), // CRY
184    (1, 1, 1), // CRZ
185    (0, 2, 2), // FSIM
186];
187
188const ALL_STANDARD_GATES: [StandardGate; 36] = [
189    StandardGate::I,
190    StandardGate::H,
191    StandardGate::RX,
192    StandardGate::RXX,
193    StandardGate::RXY,
194    StandardGate::RY,
195    StandardGate::RYY,
196    StandardGate::RZ,
197    StandardGate::RZX,
198    StandardGate::RZZ,
199    StandardGate::S,
200    StandardGate::SDG,
201    StandardGate::SWAP,
202    StandardGate::T,
203    StandardGate::TDG,
204    StandardGate::U,
205    StandardGate::X,
206    StandardGate::XY,
207    StandardGate::X2P,
208    StandardGate::X2M,
209    StandardGate::XY2P,
210    StandardGate::XY2M,
211    StandardGate::Y,
212    StandardGate::Y2P,
213    StandardGate::Y2M,
214    StandardGate::Z,
215    StandardGate::Phase,
216    StandardGate::GPhase,
217    StandardGate::CX,
218    StandardGate::CCX,
219    StandardGate::CY,
220    StandardGate::CZ,
221    StandardGate::CRX,
222    StandardGate::CRY,
223    StandardGate::CRZ,
224    StandardGate::FSIM,
225];
226
227impl fmt::Display for StandardGate {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        f.write_str(self.name())
230    }
231}
232
233impl StandardGate {
234    /// Returns the stable operation name used by circuit APIs and statistics.
235    pub const fn name(self) -> &'static str {
236        match self {
237            Self::I => "I",
238            Self::H => "H",
239            Self::RX => "RX",
240            Self::RXX => "RXX",
241            Self::RXY => "RXY",
242            Self::RY => "RY",
243            Self::RYY => "RYY",
244            Self::RZ => "RZ",
245            Self::RZX => "RZX",
246            Self::RZZ => "RZZ",
247            Self::S => "S",
248            Self::SDG => "SDG",
249            Self::SWAP => "SWAP",
250            Self::T => "T",
251            Self::TDG => "TDG",
252            Self::U => "U",
253            Self::X => "X",
254            Self::XY => "XY",
255            Self::X2P => "X2P",
256            Self::X2M => "X2M",
257            Self::XY2P => "XY2P",
258            Self::XY2M => "XY2M",
259            Self::Y => "Y",
260            Self::Y2P => "Y2P",
261            Self::Y2M => "Y2M",
262            Self::Z => "Z",
263            Self::Phase => "Phase",
264            Self::GPhase => "GPhase",
265            Self::CX => "CX",
266            Self::CCX => "CCX",
267            Self::CY => "CY",
268            Self::CZ => "CZ",
269            Self::CRX => "CRX",
270            Self::CRY => "CRY",
271            Self::CRZ => "CRZ",
272            Self::FSIM => "FSIM",
273        }
274    }
275
276    /// Returns all standard gates in enum discriminant order.
277    pub fn all() -> &'static [Self] {
278        &ALL_STANDARD_GATES
279    }
280
281    /// Returns the unitary matrix representation of the gate.
282    ///
283    /// The matrix is returned as a `Cow<Array2<Complex<f64>>>`.
284    /// - Returns `Cow::Borrowed` for static gates (like H, X, CX) to avoid allocation.
285    /// - Returns `Cow::Owned` for parametric gates (like RX, U) where the matrix is computed on the fly.
286    ///
287    /// # Arguments
288    ///
289    /// * `params` - A slice of floating-point parameters associated with the gate.
290    ///   For non-parametric gates, this can be empty. For parametric gates, it must
291    ///   contain the correct number of values (e.g., 1 for RX, 3 for U).
292    ///
293    /// # Panics
294    ///
295    /// Panics if `params` does not contain enough values for the specific gate type.
296    pub fn matrix(&self, params: &[f64]) -> Result<Cow<'_, Array2<Complex<f64>>>, CircuitError> {
297        // Validate all parameters are finite
298        for (i, &p) in params.iter().enumerate() {
299            if !p.is_finite() {
300                return Err(CircuitError::InvalidParameterValue(i, p));
301            }
302        }
303
304        Ok(match self {
305            Self::H => Cow::Borrowed(&gate_matrix::H_GATE),
306            Self::I => Cow::Borrowed(&gate_matrix::I_GATE),
307            Self::S => Cow::Borrowed(&gate_matrix::S_GATE),
308            Self::SDG => Cow::Borrowed(&gate_matrix::SDG_GATE),
309            Self::T => Cow::Borrowed(&gate_matrix::T_GATE),
310            Self::TDG => Cow::Borrowed(&gate_matrix::TDG_GATE),
311            Self::X => Cow::Borrowed(&gate_matrix::X_GATE),
312            Self::Y => Cow::Borrowed(&gate_matrix::Y_GATE),
313            Self::Z => Cow::Borrowed(&gate_matrix::Z_GATE),
314            Self::X2P => Cow::Borrowed(&gate_matrix::X2P_GATE),
315            Self::X2M => Cow::Borrowed(&gate_matrix::X2M_GATE),
316            Self::Y2P => Cow::Borrowed(&gate_matrix::Y2P_GATE),
317            Self::Y2M => Cow::Borrowed(&gate_matrix::Y2M_GATE),
318            Self::SWAP => Cow::Borrowed(&gate_matrix::SWAP_GATE),
319            Self::CX => Cow::Borrowed(&gate_matrix::CX_GATE),
320            Self::CY => Cow::Borrowed(&gate_matrix::CY_GATE),
321            Self::CZ => Cow::Borrowed(&gate_matrix::CZ_GATE),
322            Self::CCX => Cow::Borrowed(&gate_matrix::CCX_GATE),
323
324            Self::RX => Cow::Owned(gate_matrix::rx_gate(params[0])),
325            Self::RY => Cow::Owned(gate_matrix::ry_gate(params[0])),
326            Self::RZ => Cow::Owned(gate_matrix::rz_gate(params[0])),
327            Self::Phase => Cow::Owned(gate_matrix::phase_gate(params[0])),
328            Self::GPhase => Cow::Owned(gate_matrix::global_phase_gate(params[0])),
329            Self::RXX => Cow::Owned(gate_matrix::rxx_gate(params[0])),
330            Self::RYY => Cow::Owned(gate_matrix::ryy_gate(params[0])),
331            Self::RZZ => Cow::Owned(gate_matrix::rzz_gate(params[0])),
332            Self::RZX => Cow::Owned(gate_matrix::rzx_gate(params[0])),
333            Self::CRX => Cow::Owned(gate_matrix::crx_gate(params[0])),
334            Self::CRY => Cow::Owned(gate_matrix::cry_gate(params[0])),
335            Self::CRZ => Cow::Owned(gate_matrix::crz_gate(params[0])),
336            Self::XY => Cow::Owned(gate_matrix::xy_gate(params[0])),
337            Self::XY2P => Cow::Owned(gate_matrix::xy2p_gate(params[0])),
338            Self::XY2M => Cow::Owned(gate_matrix::xy2m_gate(params[0])),
339
340            Self::RXY => Cow::Owned(gate_matrix::rxy_gate(params[0], params[1])),
341            Self::FSIM => Cow::Owned(gate_matrix::fsim_gate(params[0], params[1])),
342
343            Self::U => Cow::Owned(gate_matrix::u_gate(params[0], params[1], params[2])),
344        })
345    }
346
347    /// Computes the inverse (Hermitian conjugate) of the gate.
348    ///
349    /// # Arguments
350    ///
351    /// * `params` - The parameters of the gate instance to be inverted.
352    ///
353    /// # Returns
354    ///
355    /// Returns an `Option` containing a tuple:
356    /// - `StandardGate`: The type of the inverse gate.
357    /// - `SmallVec<[Parameter; 3]>`: The transformed parameters for the inverse gate.
358    ///
359    /// Returns `None` if the inverse cannot be represented as a StandardGate (rare).
360    pub fn inverse(
361        &self,
362        params: &[Parameter],
363    ) -> Option<(StandardGate, SmallVec<[Parameter; 3]>)> {
364        match self {
365            // Self-inverse gates (U = U†)
366            Self::H => Some((Self::H, smallvec![])),
367            Self::I => Some((Self::I, smallvec![])),
368            Self::X => Some((Self::X, smallvec![])),
369            Self::Y => Some((Self::Y, smallvec![])),
370            Self::Z => Some((Self::Z, smallvec![])),
371            Self::CX => Some((Self::CX, smallvec![])),
372            Self::CCX => Some((Self::CCX, smallvec![])),
373            Self::CY => Some((Self::CY, smallvec![])),
374            Self::CZ => Some((Self::CZ, smallvec![])),
375            Self::SWAP => Some((Self::SWAP, smallvec![])),
376
377            // Paired gates (A = B†)
378            Self::S => Some((Self::SDG, smallvec![])),
379            Self::SDG => Some((Self::S, smallvec![])),
380            Self::T => Some((Self::TDG, smallvec![])),
381            Self::TDG => Some((Self::T, smallvec![])),
382            Self::X2P => Some((Self::X2M, smallvec![])),
383            Self::X2M => Some((Self::X2P, smallvec![])),
384            Self::Y2P => Some((Self::Y2M, smallvec![])),
385            Self::Y2M => Some((Self::Y2P, smallvec![])),
386            Self::XY2P => Some((Self::XY2M, smallvec![params[0].clone()])),
387            Self::XY2M => Some((Self::XY2P, smallvec![params[0].clone()])),
388
389            // Parametric gates: usually negate the parameter
390            // RX(theta)† = RX(-theta)
391            Self::RX => Some((Self::RX, smallvec![-1.0 * params[0].clone()])),
392            Self::RY => Some((Self::RY, smallvec![-1.0 * params[0].clone()])),
393            Self::RZ => Some((Self::RZ, smallvec![-1.0 * params[0].clone()])),
394            Self::Phase => Some((Self::Phase, smallvec![-1.0 * params[0].clone()])),
395            Self::GPhase => Some((Self::GPhase, smallvec![-1.0 * params[0].clone()])),
396            Self::RXX => Some((Self::RXX, smallvec![-1.0 * params[0].clone()])),
397            Self::RYY => Some((Self::RYY, smallvec![-1.0 * params[0].clone()])),
398            Self::RZZ => Some((Self::RZZ, smallvec![-1.0 * params[0].clone()])),
399            Self::RZX => Some((Self::RZX, smallvec![-1.0 * params[0].clone()])),
400
401            // Controlled rotations
402            Self::CRX => Some((Self::CRX, smallvec![-1.0 * params[0].clone()])),
403            Self::CRY => Some((Self::CRY, smallvec![-1.0 * params[0].clone()])),
404            Self::CRZ => Some((Self::CRZ, smallvec![-1.0 * params[0].clone()])),
405
406            // Two-parameter gates
407            // RXY(theta, phi) -> Rotation axis is defined by phi.
408            // Rotation amount is theta. Inverse is RXY(-theta, phi).
409            Self::RXY => Some((
410                Self::RXY,
411                smallvec![-1.0 * params[0].clone(), params[1].clone()],
412            )),
413
414            // fSim(theta, phi) -> Inverse is fSim(-theta, -phi)
415            Self::FSIM => Some((
416                Self::FSIM,
417                smallvec![-1.0 * params[0].clone(), -1.0 * params[1].clone()],
418            )),
419
420            // U(theta, phi, lambda)† = U(-theta, -lambda, -phi)
421            // Note parameter swap for phi/lambda
422            Self::U => Some((
423                Self::U,
424                smallvec![
425                    -1.0 * params[0].clone(),
426                    -1.0 * params[2].clone(),
427                    -1.0 * params[1].clone()
428                ],
429            )),
430
431            // XY gate (theta) -> Inverse is XY(pi+theta)
432            Self::XY => Some((Self::XY, smallvec![PI + params[0].clone()])),
433        }
434    }
435
436    /// Returns whether this gate is diagonal in the computational basis.
437    ///
438    /// Diagonal standard gates preserve computational basis states and only
439    /// change their phases. This is a structural property of the gate family,
440    /// independent of concrete parameter values.
441    pub fn is_diagonal(&self) -> bool {
442        matches!(
443            self,
444            Self::I
445                | Self::RZ
446                | Self::RZZ
447                | Self::S
448                | Self::SDG
449                | Self::T
450                | Self::TDG
451                | Self::Z
452                | Self::Phase
453                | Self::GPhase
454                | Self::CZ
455                | Self::CRZ
456        )
457    }
458
459    /// Returns whether exchanging the two operands leaves this two-qubit gate unchanged.
460    ///
461    /// This is an exact semantic property used by physical layout and device
462    /// lowering. It is deliberately narrower than being self-inverse or having
463    /// no control qubits: callers may reverse the ordered operands only for the
464    /// gate families listed here.
465    pub(crate) fn is_invariant_under_operand_swap(&self) -> bool {
466        matches!(
467            self,
468            Self::CZ | Self::SWAP | Self::RXX | Self::RYY | Self::RZZ | Self::FSIM
469        )
470    }
471
472    /// Returns the number of control qubits defined for this gate.
473    ///
474    /// For example:
475    /// - `X` -> 0
476    /// - `CX` -> 1
477    /// - `CCX` -> 2
478    pub fn num_ctrl_qubits(&self) -> usize {
479        GATE_INFO_TABLE[*self as usize].0 as usize
480    }
481
482    /// Returns the total number of qubits this gate acts on (controls + targets).
483    ///
484    /// For example:
485    /// - `X` -> 1
486    /// - `CX` -> 2 (1 control + 1 target)
487    /// - `SWAP` -> 2
488    pub fn num_qubits(&self) -> usize {
489        let idx = *self as usize;
490        let (c, t, _) = GATE_INFO_TABLE[idx];
491        (c + t) as usize
492    }
493
494    /// Returns the number of floating-point parameters this gate accepts.
495    ///
496    /// For example:
497    /// - `H` -> 0
498    /// - `RX` -> 1
499    /// - `U` -> 3
500    pub fn num_params(&self) -> usize {
501        GATE_INFO_TABLE[*self as usize].2 as usize
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::StandardGate;
508
509    #[test]
510    fn operand_swap_invariance_is_exhaustive_for_two_qubit_standard_gates() {
511        let expected = [
512            StandardGate::RXX,
513            StandardGate::RYY,
514            StandardGate::RZZ,
515            StandardGate::SWAP,
516            StandardGate::CZ,
517            StandardGate::FSIM,
518        ];
519
520        for gate in StandardGate::all()
521            .iter()
522            .copied()
523            .filter(|gate| gate.num_qubits() == 2)
524        {
525            assert_eq!(
526                gate.is_invariant_under_operand_swap(),
527                expected.contains(&gate),
528                "unexpected operand-swap classification for {gate}"
529            );
530        }
531    }
532
533    #[test]
534    fn classified_two_qubit_gates_are_matrix_invariant_under_operand_swap() {
535        let cases: &[(StandardGate, &[f64])] = &[
536            (StandardGate::RXX, &[0.37]),
537            (StandardGate::RYY, &[-0.41]),
538            (StandardGate::RZZ, &[0.23]),
539            (StandardGate::SWAP, &[]),
540            (StandardGate::CZ, &[]),
541            (StandardGate::FSIM, &[0.31, -0.17]),
542        ];
543        let swapped_basis = [0, 2, 1, 3];
544
545        for (gate, params) in cases {
546            let matrix = gate.matrix(params).unwrap();
547            for row in 0..4 {
548                for column in 0..4 {
549                    let swapped = matrix[[swapped_basis[row], swapped_basis[column]]];
550                    assert!(
551                        (matrix[[row, column]] - swapped).norm() < 1e-12,
552                        "{gate} is not invariant at ({row}, {column})"
553                    );
554                }
555            }
556        }
557    }
558
559    #[test]
560    fn xy_pulse_family_remains_single_qubit() {
561        for gate in [StandardGate::XY, StandardGate::XY2P, StandardGate::XY2M] {
562            assert_eq!(gate.num_qubits(), 1);
563            assert_eq!(gate.matrix(&[0.37]).unwrap().dim(), (2, 2));
564            assert!(!gate.is_invariant_under_operand_swap());
565        }
566    }
567}