use crate::complex::Complex64;
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Gate1 {
pub m: [Complex64; 4],
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Gate2 {
pub m: [Complex64; 16],
}
const fn c(re: f64, im: f64) -> Complex64 {
Complex64::new(re, im)
}
const ZERO: Complex64 = Complex64::ZERO;
const ONE: Complex64 = Complex64::ONE;
impl Gate1 {
#[inline]
#[must_use]
pub const fn new(m00: Complex64, m01: Complex64, m10: Complex64, m11: Complex64) -> Self {
Self {
m: [m00, m01, m10, m11],
}
}
#[must_use]
pub const fn id() -> Self {
Self::new(ONE, ZERO, ZERO, ONE)
}
#[must_use]
pub const fn x() -> Self {
Self::new(ZERO, ONE, ONE, ZERO)
}
#[must_use]
pub const fn y() -> Self {
Self::new(ZERO, c(0.0, -1.0), c(0.0, 1.0), ZERO)
}
#[must_use]
pub const fn z() -> Self {
Self::new(ONE, ZERO, ZERO, c(-1.0, 0.0))
}
#[must_use]
pub fn h() -> Self {
let s = std::f64::consts::FRAC_1_SQRT_2;
Self::new(c(s, 0.0), c(s, 0.0), c(s, 0.0), c(-s, 0.0))
}
#[must_use]
pub const fn s() -> Self {
Self::new(ONE, ZERO, ZERO, Complex64::I)
}
#[must_use]
pub fn t() -> Self {
Self::new(
ONE,
ZERO,
ZERO,
Complex64::expi(std::f64::consts::FRAC_PI_4),
)
}
#[must_use]
pub fn rx(theta: f64) -> Self {
let (s, co) = (theta / 2.0).sin_cos();
Self::new(c(co, 0.0), c(0.0, -s), c(0.0, -s), c(co, 0.0))
}
#[must_use]
pub fn ry(theta: f64) -> Self {
let (s, co) = (theta / 2.0).sin_cos();
Self::new(c(co, 0.0), c(-s, 0.0), c(s, 0.0), c(co, 0.0))
}
#[must_use]
pub fn rz(theta: f64) -> Self {
Self::new(
Complex64::expi(-theta / 2.0),
ZERO,
ZERO,
Complex64::expi(theta / 2.0),
)
}
#[must_use]
pub fn phase(lambda: f64) -> Self {
Self::new(ONE, ZERO, ZERO, Complex64::expi(lambda))
}
#[must_use]
pub fn adjoint(&self) -> Self {
Self::new(
self.m[0].conj(),
self.m[2].conj(),
self.m[1].conj(),
self.m[3].conj(),
)
}
#[must_use]
pub fn compose(&self, other: &Self) -> Self {
let a = &self.m;
let b = &other.m;
Self::new(
a[0] * b[0] + a[1] * b[2],
a[0] * b[1] + a[1] * b[3],
a[2] * b[0] + a[3] * b[2],
a[2] * b[1] + a[3] * b[3],
)
}
#[must_use]
pub fn is_identity(&self, eps: f64) -> bool {
let i = Self::id();
(0..4).all(|k| (self.m[k] - i.m[k]).norm() <= eps)
}
#[must_use]
pub fn is_unitary(&self, eps: f64) -> bool {
self.adjoint().compose(self).is_identity(eps)
}
}
impl Gate2 {
#[inline]
#[must_use]
pub const fn new(m: [Complex64; 16]) -> Self {
Self { m }
}
#[must_use]
pub const fn cnot() -> Self {
Self::new([
ONE, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ZERO, ONE, ZERO, ])
}
#[must_use]
pub const fn cz() -> Self {
Self::new([
ONE,
ZERO,
ZERO,
ZERO, ZERO,
ONE,
ZERO,
ZERO, ZERO,
ZERO,
ONE,
ZERO, ZERO,
ZERO,
ZERO,
c(-1.0, 0.0),
])
}
#[must_use]
pub const fn swap() -> Self {
Self::new([
ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ZERO, ONE, ZERO, ZERO, ZERO, ZERO, ZERO, ONE,
])
}
#[must_use]
pub fn controlled(u: &Gate1) -> Self {
Self::new([
ONE, ZERO, ZERO, ZERO, ZERO, ONE, ZERO, ZERO, ZERO, ZERO, u.m[0], u.m[1], ZERO, ZERO, u.m[2], u.m[3],
])
}
#[must_use]
pub fn adjoint(&self) -> Self {
let mut out = [Complex64::ZERO; 16];
for row in 0..4 {
for col in 0..4 {
out[row * 4 + col] = self.m[col * 4 + row].conj();
}
}
Self::new(out)
}
#[must_use]
pub fn is_unitary(&self, eps: f64) -> bool {
let prod = self.adjoint().mul(self);
for row in 0..4 {
for col in 0..4 {
let expected = if row == col { ONE } else { ZERO };
if (prod.m[row * 4 + col] - expected).norm() > eps {
return false;
}
}
}
true
}
fn mul(&self, other: &Self) -> Self {
let mut out = [Complex64::ZERO; 16];
for row in 0..4 {
for col in 0..4 {
let mut acc = Complex64::ZERO;
for k in 0..4 {
acc += self.m[row * 4 + k] * other.m[k * 4 + col];
}
out[row * 4 + col] = acc;
}
}
Self::new(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paulis_are_unitary() {
for g in [
Gate1::x(),
Gate1::y(),
Gate1::z(),
Gate1::h(),
Gate1::s(),
Gate1::t(),
] {
assert!(g.is_unitary(1e-12));
}
}
#[test]
fn pauli_x_is_its_own_inverse() {
assert!(Gate1::x().compose(&Gate1::x()).is_identity(1e-12));
}
#[test]
fn s_squared_is_z() {
let s2 = Gate1::s().compose(&Gate1::s());
assert!(s2.compose(&Gate1::z().adjoint()).is_identity(1e-12));
}
#[test]
fn t_to_the_fourth_is_z() {
let t = Gate1::t();
let t4 = t.compose(&t).compose(&t).compose(&t);
assert!(t4.compose(&Gate1::z().adjoint()).is_identity(1e-12));
}
#[test]
fn rz_pi_matches_z_up_to_global_phase() {
let rz = Gate1::rz(std::f64::consts::PI);
let z = Gate1::z();
let adjusted = Gate1::new(
rz.m[0] * Complex64::I,
rz.m[1] * Complex64::I,
rz.m[2] * Complex64::I,
rz.m[3] * Complex64::I,
);
assert!(adjusted.compose(&z.adjoint()).is_identity(1e-12));
}
#[test]
fn two_qubit_gates_are_unitary() {
for g in [Gate2::cnot(), Gate2::cz(), Gate2::swap()] {
assert!(g.is_unitary(1e-12));
}
}
#[test]
fn controlled_x_is_cnot() {
assert!(Gate2::controlled(&Gate1::x()).is_unitary(1e-12));
assert_eq!(Gate2::controlled(&Gate1::x()), Gate2::cnot());
}
#[test]
fn controlled_z_matches_cz() {
assert_eq!(Gate2::controlled(&Gate1::z()), Gate2::cz());
}
}