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}