use crate::circuit::gate::gate_matrix;
use crate::circuit::{CircuitError, Parameter};
use ndarray::prelude::*;
use num::complex::Complex;
use smallvec::{SmallVec, smallvec};
use std::borrow::Cow;
use std::f64::consts::PI;
use std::fmt;
use std::hash::Hash;
#[repr(u8)]
#[derive(Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default, Clone, Copy)]
pub enum StandardGate {
#[default]
I = 0,
H = 1,
RX,
RXX,
RXY,
RY,
RYY,
RZ,
RZX,
RZZ,
S,
SDG,
SWAP,
T,
TDG,
U,
X,
XY,
X2P,
X2M,
XY2P,
XY2M,
Y,
Y2P,
Y2M,
Z,
Phase,
GPhase,
CX,
CCX,
CY,
CZ,
CRX,
CRY,
CRZ,
FSIM,
}
const GATE_INFO_TABLE: [(u8, u8, u8); 36] = [
(0, 1, 0), (0, 1, 0), (0, 1, 1), (0, 2, 1), (0, 1, 2), (0, 1, 1), (0, 2, 1), (0, 1, 1), (0, 2, 1), (0, 2, 1), (0, 1, 0), (0, 1, 0), (0, 2, 0), (0, 1, 0), (0, 1, 0), (0, 1, 3), (0, 1, 0), (0, 1, 1), (0, 1, 0), (0, 1, 0), (0, 1, 1), (0, 1, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1), (1, 1, 0), (2, 1, 0), (1, 1, 0), (1, 1, 0), (1, 1, 1), (1, 1, 1), (1, 1, 1), (0, 2, 2), ];
const ALL_STANDARD_GATES: [StandardGate; 36] = [
StandardGate::I,
StandardGate::H,
StandardGate::RX,
StandardGate::RXX,
StandardGate::RXY,
StandardGate::RY,
StandardGate::RYY,
StandardGate::RZ,
StandardGate::RZX,
StandardGate::RZZ,
StandardGate::S,
StandardGate::SDG,
StandardGate::SWAP,
StandardGate::T,
StandardGate::TDG,
StandardGate::U,
StandardGate::X,
StandardGate::XY,
StandardGate::X2P,
StandardGate::X2M,
StandardGate::XY2P,
StandardGate::XY2M,
StandardGate::Y,
StandardGate::Y2P,
StandardGate::Y2M,
StandardGate::Z,
StandardGate::Phase,
StandardGate::GPhase,
StandardGate::CX,
StandardGate::CCX,
StandardGate::CY,
StandardGate::CZ,
StandardGate::CRX,
StandardGate::CRY,
StandardGate::CRZ,
StandardGate::FSIM,
];
impl fmt::Display for StandardGate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
impl StandardGate {
pub const fn name(self) -> &'static str {
match self {
Self::I => "I",
Self::H => "H",
Self::RX => "RX",
Self::RXX => "RXX",
Self::RXY => "RXY",
Self::RY => "RY",
Self::RYY => "RYY",
Self::RZ => "RZ",
Self::RZX => "RZX",
Self::RZZ => "RZZ",
Self::S => "S",
Self::SDG => "SDG",
Self::SWAP => "SWAP",
Self::T => "T",
Self::TDG => "TDG",
Self::U => "U",
Self::X => "X",
Self::XY => "XY",
Self::X2P => "X2P",
Self::X2M => "X2M",
Self::XY2P => "XY2P",
Self::XY2M => "XY2M",
Self::Y => "Y",
Self::Y2P => "Y2P",
Self::Y2M => "Y2M",
Self::Z => "Z",
Self::Phase => "Phase",
Self::GPhase => "GPhase",
Self::CX => "CX",
Self::CCX => "CCX",
Self::CY => "CY",
Self::CZ => "CZ",
Self::CRX => "CRX",
Self::CRY => "CRY",
Self::CRZ => "CRZ",
Self::FSIM => "FSIM",
}
}
pub fn all() -> &'static [Self] {
&ALL_STANDARD_GATES
}
pub fn matrix(&self, params: &[f64]) -> Result<Cow<'_, Array2<Complex<f64>>>, CircuitError> {
for (i, &p) in params.iter().enumerate() {
if !p.is_finite() {
return Err(CircuitError::InvalidParameterValue(i, p));
}
}
Ok(match self {
Self::H => Cow::Borrowed(&gate_matrix::H_GATE),
Self::I => Cow::Borrowed(&gate_matrix::I_GATE),
Self::S => Cow::Borrowed(&gate_matrix::S_GATE),
Self::SDG => Cow::Borrowed(&gate_matrix::SDG_GATE),
Self::T => Cow::Borrowed(&gate_matrix::T_GATE),
Self::TDG => Cow::Borrowed(&gate_matrix::TDG_GATE),
Self::X => Cow::Borrowed(&gate_matrix::X_GATE),
Self::Y => Cow::Borrowed(&gate_matrix::Y_GATE),
Self::Z => Cow::Borrowed(&gate_matrix::Z_GATE),
Self::X2P => Cow::Borrowed(&gate_matrix::X2P_GATE),
Self::X2M => Cow::Borrowed(&gate_matrix::X2M_GATE),
Self::Y2P => Cow::Borrowed(&gate_matrix::Y2P_GATE),
Self::Y2M => Cow::Borrowed(&gate_matrix::Y2M_GATE),
Self::SWAP => Cow::Borrowed(&gate_matrix::SWAP_GATE),
Self::CX => Cow::Borrowed(&gate_matrix::CX_GATE),
Self::CY => Cow::Borrowed(&gate_matrix::CY_GATE),
Self::CZ => Cow::Borrowed(&gate_matrix::CZ_GATE),
Self::CCX => Cow::Borrowed(&gate_matrix::CCX_GATE),
Self::RX => Cow::Owned(gate_matrix::rx_gate(params[0])),
Self::RY => Cow::Owned(gate_matrix::ry_gate(params[0])),
Self::RZ => Cow::Owned(gate_matrix::rz_gate(params[0])),
Self::Phase => Cow::Owned(gate_matrix::phase_gate(params[0])),
Self::GPhase => Cow::Owned(gate_matrix::global_phase_gate(params[0])),
Self::RXX => Cow::Owned(gate_matrix::rxx_gate(params[0])),
Self::RYY => Cow::Owned(gate_matrix::ryy_gate(params[0])),
Self::RZZ => Cow::Owned(gate_matrix::rzz_gate(params[0])),
Self::RZX => Cow::Owned(gate_matrix::rzx_gate(params[0])),
Self::CRX => Cow::Owned(gate_matrix::crx_gate(params[0])),
Self::CRY => Cow::Owned(gate_matrix::cry_gate(params[0])),
Self::CRZ => Cow::Owned(gate_matrix::crz_gate(params[0])),
Self::XY => Cow::Owned(gate_matrix::xy_gate(params[0])),
Self::XY2P => Cow::Owned(gate_matrix::xy2p_gate(params[0])),
Self::XY2M => Cow::Owned(gate_matrix::xy2m_gate(params[0])),
Self::RXY => Cow::Owned(gate_matrix::rxy_gate(params[0], params[1])),
Self::FSIM => Cow::Owned(gate_matrix::fsim_gate(params[0], params[1])),
Self::U => Cow::Owned(gate_matrix::u_gate(params[0], params[1], params[2])),
})
}
pub fn inverse(
&self,
params: &[Parameter],
) -> Option<(StandardGate, SmallVec<[Parameter; 3]>)> {
match self {
Self::H => Some((Self::H, smallvec![])),
Self::I => Some((Self::I, smallvec![])),
Self::X => Some((Self::X, smallvec![])),
Self::Y => Some((Self::Y, smallvec![])),
Self::Z => Some((Self::Z, smallvec![])),
Self::CX => Some((Self::CX, smallvec![])),
Self::CCX => Some((Self::CCX, smallvec![])),
Self::CY => Some((Self::CY, smallvec![])),
Self::CZ => Some((Self::CZ, smallvec![])),
Self::SWAP => Some((Self::SWAP, smallvec![])),
Self::S => Some((Self::SDG, smallvec![])),
Self::SDG => Some((Self::S, smallvec![])),
Self::T => Some((Self::TDG, smallvec![])),
Self::TDG => Some((Self::T, smallvec![])),
Self::X2P => Some((Self::X2M, smallvec![])),
Self::X2M => Some((Self::X2P, smallvec![])),
Self::Y2P => Some((Self::Y2M, smallvec![])),
Self::Y2M => Some((Self::Y2P, smallvec![])),
Self::XY2P => Some((Self::XY2M, smallvec![params[0].clone()])),
Self::XY2M => Some((Self::XY2P, smallvec![params[0].clone()])),
Self::RX => Some((Self::RX, smallvec![-1.0 * params[0].clone()])),
Self::RY => Some((Self::RY, smallvec![-1.0 * params[0].clone()])),
Self::RZ => Some((Self::RZ, smallvec![-1.0 * params[0].clone()])),
Self::Phase => Some((Self::Phase, smallvec![-1.0 * params[0].clone()])),
Self::GPhase => Some((Self::GPhase, smallvec![-1.0 * params[0].clone()])),
Self::RXX => Some((Self::RXX, smallvec![-1.0 * params[0].clone()])),
Self::RYY => Some((Self::RYY, smallvec![-1.0 * params[0].clone()])),
Self::RZZ => Some((Self::RZZ, smallvec![-1.0 * params[0].clone()])),
Self::RZX => Some((Self::RZX, smallvec![-1.0 * params[0].clone()])),
Self::CRX => Some((Self::CRX, smallvec![-1.0 * params[0].clone()])),
Self::CRY => Some((Self::CRY, smallvec![-1.0 * params[0].clone()])),
Self::CRZ => Some((Self::CRZ, smallvec![-1.0 * params[0].clone()])),
Self::RXY => Some((
Self::RXY,
smallvec![-1.0 * params[0].clone(), params[1].clone()],
)),
Self::FSIM => Some((
Self::FSIM,
smallvec![-1.0 * params[0].clone(), -1.0 * params[1].clone()],
)),
Self::U => Some((
Self::U,
smallvec![
-1.0 * params[0].clone(),
-1.0 * params[2].clone(),
-1.0 * params[1].clone()
],
)),
Self::XY => Some((Self::XY, smallvec![PI + params[0].clone()])),
}
}
pub fn is_diagonal(&self) -> bool {
matches!(
self,
Self::I
| Self::RZ
| Self::RZZ
| Self::S
| Self::SDG
| Self::T
| Self::TDG
| Self::Z
| Self::Phase
| Self::GPhase
| Self::CZ
| Self::CRZ
)
}
pub(crate) fn is_invariant_under_operand_swap(&self) -> bool {
matches!(
self,
Self::CZ | Self::SWAP | Self::RXX | Self::RYY | Self::RZZ | Self::FSIM
)
}
pub fn num_ctrl_qubits(&self) -> usize {
GATE_INFO_TABLE[*self as usize].0 as usize
}
pub fn num_qubits(&self) -> usize {
let idx = *self as usize;
let (c, t, _) = GATE_INFO_TABLE[idx];
(c + t) as usize
}
pub fn num_params(&self) -> usize {
GATE_INFO_TABLE[*self as usize].2 as usize
}
}
#[cfg(test)]
mod tests {
use super::StandardGate;
#[test]
fn operand_swap_invariance_is_exhaustive_for_two_qubit_standard_gates() {
let expected = [
StandardGate::RXX,
StandardGate::RYY,
StandardGate::RZZ,
StandardGate::SWAP,
StandardGate::CZ,
StandardGate::FSIM,
];
for gate in StandardGate::all()
.iter()
.copied()
.filter(|gate| gate.num_qubits() == 2)
{
assert_eq!(
gate.is_invariant_under_operand_swap(),
expected.contains(&gate),
"unexpected operand-swap classification for {gate}"
);
}
}
#[test]
fn classified_two_qubit_gates_are_matrix_invariant_under_operand_swap() {
let cases: &[(StandardGate, &[f64])] = &[
(StandardGate::RXX, &[0.37]),
(StandardGate::RYY, &[-0.41]),
(StandardGate::RZZ, &[0.23]),
(StandardGate::SWAP, &[]),
(StandardGate::CZ, &[]),
(StandardGate::FSIM, &[0.31, -0.17]),
];
let swapped_basis = [0, 2, 1, 3];
for (gate, params) in cases {
let matrix = gate.matrix(params).unwrap();
for row in 0..4 {
for column in 0..4 {
let swapped = matrix[[swapped_basis[row], swapped_basis[column]]];
assert!(
(matrix[[row, column]] - swapped).norm() < 1e-12,
"{gate} is not invariant at ({row}, {column})"
);
}
}
}
}
#[test]
fn xy_pulse_family_remains_single_qubit() {
for gate in [StandardGate::XY, StandardGate::XY2P, StandardGate::XY2M] {
assert_eq!(gate.num_qubits(), 1);
assert_eq!(gate.matrix(&[0.37]).unwrap().dim(), (2, 2));
assert!(!gate.is_invariant_under_operand_swap());
}
}
}