@qbead/bloch-sphere
Version:
A 3D Bloch Sphere visualisation built with Three.js and TypeScript.
490 lines (453 loc) • 17.8 kB
text/typescript
import { describe, expect, test } from 'bun:test'
import * as gates from './gates'
import { Complex } from './complex'
describe('Quantum Gates', () => {
describe('identity()', () => {
test('returns identity matrix', () => {
const id = gates.identity()
expect(id.a.real).toBe(1)
expect(id.a.imag).toBe(0)
expect(id.b.real).toBe(0)
expect(id.b.imag).toBe(0)
expect(id.c.real).toBe(0)
expect(id.c.imag).toBe(0)
expect(id.d.real).toBe(1)
expect(id.d.imag).toBe(0)
})
test('identity squared is identity', () => {
const id = gates.identity()
const id2 = id.times(id)
expect(id2.a.real).toBeCloseTo(1, 10)
expect(id2.a.imag).toBeCloseTo(0, 10)
expect(id2.b.real).toBeCloseTo(0, 10)
expect(id2.b.imag).toBeCloseTo(0, 10)
expect(id2.c.real).toBeCloseTo(0, 10)
expect(id2.c.imag).toBeCloseTo(0, 10)
expect(id2.d.real).toBeCloseTo(1, 10)
expect(id2.d.imag).toBeCloseTo(0, 10)
})
})
describe('Pauli X gate', () => {
test('x() returns correct matrix [[0, 1], [1, 0]]', () => {
const X = gates.x()
expect(X.a.real).toBe(0)
expect(X.a.imag).toBe(0)
expect(X.b.real).toBe(1)
expect(X.b.imag).toBe(0)
expect(X.c.real).toBe(1)
expect(X.c.imag).toBe(0)
expect(X.d.real).toBe(0)
expect(X.d.imag).toBe(0)
})
test('X² = I (X squared is identity)', () => {
const X = gates.x()
const X2 = X.times(X)
expect(X2.a.real).toBeCloseTo(1, 10)
expect(X2.a.imag).toBeCloseTo(0, 10)
expect(X2.b.real).toBeCloseTo(0, 10)
expect(X2.b.imag).toBeCloseTo(0, 10)
expect(X2.c.real).toBeCloseTo(0, 10)
expect(X2.c.imag).toBeCloseTo(0, 10)
expect(X2.d.real).toBeCloseTo(1, 10)
expect(X2.d.imag).toBeCloseTo(0, 10)
})
test('X is Hermitian (X† = X)', () => {
const X = gates.x()
const Xdag = X.conjugateTranspose()
expect(Xdag.a.real).toBeCloseTo(X.a.real, 10)
expect(Xdag.a.imag).toBeCloseTo(X.a.imag, 10)
expect(Xdag.b.real).toBeCloseTo(X.b.real, 10)
expect(Xdag.b.imag).toBeCloseTo(X.b.imag, 10)
expect(Xdag.c.real).toBeCloseTo(X.c.real, 10)
expect(Xdag.c.imag).toBeCloseTo(X.c.imag, 10)
expect(Xdag.d.real).toBeCloseTo(X.d.real, 10)
expect(Xdag.d.imag).toBeCloseTo(X.d.imag, 10)
})
test('X is unitary (X†X = I)', () => {
const X = gates.x()
const XdagX = X.conjugateTranspose().times(X)
expect(XdagX.a.real).toBeCloseTo(1, 10)
expect(XdagX.a.imag).toBeCloseTo(0, 10)
expect(XdagX.b.real).toBeCloseTo(0, 10)
expect(XdagX.b.imag).toBeCloseTo(0, 10)
expect(XdagX.c.real).toBeCloseTo(0, 10)
expect(XdagX.c.imag).toBeCloseTo(0, 10)
expect(XdagX.d.real).toBeCloseTo(1, 10)
expect(XdagX.d.imag).toBeCloseTo(0, 10)
})
test('not() is alias for x()', () => {
const X = gates.x()
const NOT = gates.not()
expect(NOT.a.real).toBe(X.a.real)
expect(NOT.a.imag).toBe(X.a.imag)
expect(NOT.b.real).toBe(X.b.real)
expect(NOT.b.imag).toBe(X.b.imag)
expect(NOT.c.real).toBe(X.c.real)
expect(NOT.c.imag).toBe(X.c.imag)
expect(NOT.d.real).toBe(X.d.real)
expect(NOT.d.imag).toBe(X.d.imag)
})
})
describe('Pauli Y gate', () => {
test('y() returns correct matrix [[0, -i], [i, 0]]', () => {
const Y = gates.y()
expect(Math.abs(Y.a.real)).toBe(0)
expect(Math.abs(Y.a.imag)).toBe(0)
expect(Math.abs(Y.b.real)).toBe(0)
expect(Y.b.imag).toBe(-1)
expect(Math.abs(Y.c.real)).toBe(0)
expect(Y.c.imag).toBe(1)
expect(Math.abs(Y.d.real)).toBe(0)
expect(Math.abs(Y.d.imag)).toBe(0)
})
test('Y² = I (Y squared is identity)', () => {
const Y = gates.y()
const Y2 = Y.times(Y)
expect(Y2.a.real).toBeCloseTo(1, 10)
expect(Y2.a.imag).toBeCloseTo(0, 10)
expect(Y2.b.real).toBeCloseTo(0, 10)
expect(Y2.b.imag).toBeCloseTo(0, 10)
expect(Y2.c.real).toBeCloseTo(0, 10)
expect(Y2.c.imag).toBeCloseTo(0, 10)
expect(Y2.d.real).toBeCloseTo(1, 10)
expect(Y2.d.imag).toBeCloseTo(0, 10)
})
test('Y is Hermitian (Y† = Y)', () => {
const Y = gates.y()
const Ydag = Y.conjugateTranspose()
expect(Ydag.a.real).toBeCloseTo(Y.a.real, 10)
expect(Ydag.a.imag).toBeCloseTo(Y.a.imag, 10)
expect(Ydag.b.real).toBeCloseTo(Y.b.real, 10)
expect(Ydag.b.imag).toBeCloseTo(Y.b.imag, 10)
expect(Ydag.c.real).toBeCloseTo(Y.c.real, 10)
expect(Ydag.c.imag).toBeCloseTo(Y.c.imag, 10)
expect(Ydag.d.real).toBeCloseTo(Y.d.real, 10)
expect(Ydag.d.imag).toBeCloseTo(Y.d.imag, 10)
})
test('Y is unitary (Y†Y = I)', () => {
const Y = gates.y()
const YdagY = Y.conjugateTranspose().times(Y)
expect(YdagY.a.real).toBeCloseTo(1, 10)
expect(YdagY.a.imag).toBeCloseTo(0, 10)
expect(YdagY.b.real).toBeCloseTo(0, 10)
expect(YdagY.b.imag).toBeCloseTo(0, 10)
expect(YdagY.c.real).toBeCloseTo(0, 10)
expect(YdagY.c.imag).toBeCloseTo(0, 10)
expect(YdagY.d.real).toBeCloseTo(1, 10)
expect(YdagY.d.imag).toBeCloseTo(0, 10)
})
})
describe('Pauli Z gate', () => {
test('z() returns correct matrix [[1, 0], [0, -1]]', () => {
const Z = gates.z()
expect(Z.a.real).toBe(1)
expect(Z.a.imag).toBe(0)
expect(Z.b.real).toBe(0)
expect(Z.b.imag).toBe(0)
expect(Z.c.real).toBe(0)
expect(Z.c.imag).toBe(0)
expect(Z.d.real).toBe(-1)
expect(Z.d.imag).toBe(0)
})
test('Z² = I (Z squared is identity)', () => {
const Z = gates.z()
const Z2 = Z.times(Z)
expect(Z2.a.real).toBeCloseTo(1, 10)
expect(Z2.a.imag).toBeCloseTo(0, 10)
expect(Z2.b.real).toBeCloseTo(0, 10)
expect(Z2.b.imag).toBeCloseTo(0, 10)
expect(Z2.c.real).toBeCloseTo(0, 10)
expect(Z2.c.imag).toBeCloseTo(0, 10)
expect(Z2.d.real).toBeCloseTo(1, 10)
expect(Z2.d.imag).toBeCloseTo(0, 10)
})
test('Z is Hermitian (Z† = Z)', () => {
const Z = gates.z()
const Zdag = Z.conjugateTranspose()
expect(Zdag.a.real).toBeCloseTo(Z.a.real, 10)
expect(Zdag.a.imag).toBeCloseTo(Z.a.imag, 10)
expect(Zdag.b.real).toBeCloseTo(Z.b.real, 10)
expect(Zdag.b.imag).toBeCloseTo(Z.b.imag, 10)
expect(Zdag.c.real).toBeCloseTo(Z.c.real, 10)
expect(Zdag.c.imag).toBeCloseTo(Z.c.imag, 10)
expect(Zdag.d.real).toBeCloseTo(Z.d.real, 10)
expect(Zdag.d.imag).toBeCloseTo(Z.d.imag, 10)
})
test('Z is unitary (Z†Z = I)', () => {
const Z = gates.z()
const ZdagZ = Z.conjugateTranspose().times(Z)
expect(ZdagZ.a.real).toBeCloseTo(1, 10)
expect(ZdagZ.a.imag).toBeCloseTo(0, 10)
expect(ZdagZ.b.real).toBeCloseTo(0, 10)
expect(ZdagZ.b.imag).toBeCloseTo(0, 10)
expect(ZdagZ.c.real).toBeCloseTo(0, 10)
expect(ZdagZ.c.imag).toBeCloseTo(0, 10)
expect(ZdagZ.d.real).toBeCloseTo(1, 10)
expect(ZdagZ.d.imag).toBeCloseTo(0, 10)
})
})
describe('Hadamard gate', () => {
test('hadamard() returns matrix (1/√2)[[1, 1], [1, -1]]', () => {
const H = gates.hadamard()
const sqrt2 = 1 / Math.sqrt(2)
expect(H.a.real).toBeCloseTo(sqrt2, 10)
expect(H.a.imag).toBeCloseTo(0, 10)
expect(H.b.real).toBeCloseTo(sqrt2, 10)
expect(H.b.imag).toBeCloseTo(0, 10)
expect(H.c.real).toBeCloseTo(sqrt2, 10)
expect(H.c.imag).toBeCloseTo(0, 10)
expect(H.d.real).toBeCloseTo(-sqrt2, 10)
expect(H.d.imag).toBeCloseTo(0, 10)
})
test('H² = I (Hadamard squared is identity)', () => {
const H = gates.hadamard()
const H2 = H.times(H)
expect(H2.a.real).toBeCloseTo(1, 10)
expect(H2.a.imag).toBeCloseTo(0, 10)
expect(H2.b.real).toBeCloseTo(0, 10)
expect(H2.b.imag).toBeCloseTo(0, 10)
expect(H2.c.real).toBeCloseTo(0, 10)
expect(H2.c.imag).toBeCloseTo(0, 10)
expect(H2.d.real).toBeCloseTo(1, 10)
expect(H2.d.imag).toBeCloseTo(0, 10)
})
test('H is Hermitian (H† = H)', () => {
const H = gates.hadamard()
const Hdag = H.conjugateTranspose()
expect(Hdag.a.real).toBeCloseTo(H.a.real, 10)
expect(Hdag.a.imag).toBeCloseTo(H.a.imag, 10)
expect(Hdag.b.real).toBeCloseTo(H.b.real, 10)
expect(Hdag.b.imag).toBeCloseTo(H.b.imag, 10)
expect(Hdag.c.real).toBeCloseTo(H.c.real, 10)
expect(Hdag.c.imag).toBeCloseTo(H.c.imag, 10)
expect(Hdag.d.real).toBeCloseTo(H.d.real, 10)
expect(Hdag.d.imag).toBeCloseTo(H.d.imag, 10)
})
test('H is unitary (H†H = I)', () => {
const H = gates.hadamard()
const HdagH = H.conjugateTranspose().times(H)
expect(HdagH.a.real).toBeCloseTo(1, 10)
expect(HdagH.a.imag).toBeCloseTo(0, 10)
expect(HdagH.b.real).toBeCloseTo(0, 10)
expect(HdagH.b.imag).toBeCloseTo(0, 10)
expect(HdagH.c.real).toBeCloseTo(0, 10)
expect(HdagH.c.imag).toBeCloseTo(0, 10)
expect(HdagH.d.real).toBeCloseTo(1, 10)
expect(HdagH.d.imag).toBeCloseTo(0, 10)
})
})
describe('phase() gate', () => {
test('phase(0) is identity', () => {
const P = gates.phase(0)
expect(P.a.real).toBeCloseTo(1, 10)
expect(P.a.imag).toBeCloseTo(0, 10)
expect(P.b.real).toBeCloseTo(0, 10)
expect(P.b.imag).toBeCloseTo(0, 10)
expect(P.c.real).toBeCloseTo(0, 10)
expect(P.c.imag).toBeCloseTo(0, 10)
expect(P.d.real).toBeCloseTo(1, 10)
expect(P.d.imag).toBeCloseTo(0, 10)
})
test('phase(π) is Z gate', () => {
const P = gates.phase(Math.PI)
const Z = gates.z()
expect(P.a.real).toBeCloseTo(Z.a.real, 10)
expect(P.a.imag).toBeCloseTo(Z.a.imag, 10)
expect(P.b.real).toBeCloseTo(Z.b.real, 10)
expect(P.b.imag).toBeCloseTo(Z.b.imag, 10)
expect(P.c.real).toBeCloseTo(Z.c.real, 10)
expect(P.c.imag).toBeCloseTo(Z.c.imag, 10)
expect(P.d.real).toBeCloseTo(Z.d.real, 10)
expect(P.d.imag).toBeCloseTo(Z.d.imag, 10)
})
test('phase(π/2) is S gate', () => {
const S = gates.phase(Math.PI / 2)
expect(S.a.real).toBeCloseTo(1, 10)
expect(S.a.imag).toBeCloseTo(0, 10)
expect(S.b.real).toBeCloseTo(0, 10)
expect(S.b.imag).toBeCloseTo(0, 10)
expect(S.c.real).toBeCloseTo(0, 10)
expect(S.c.imag).toBeCloseTo(0, 10)
expect(S.d.real).toBeCloseTo(0, 10)
expect(S.d.imag).toBeCloseTo(1, 10)
})
test('phase gate is unitary', () => {
const P = gates.phase(Math.PI / 3)
const PdagP = P.conjugateTranspose().times(P)
expect(PdagP.a.real).toBeCloseTo(1, 10)
expect(PdagP.a.imag).toBeCloseTo(0, 10)
expect(PdagP.b.real).toBeCloseTo(0, 10)
expect(PdagP.b.imag).toBeCloseTo(0, 10)
expect(PdagP.c.real).toBeCloseTo(0, 10)
expect(PdagP.c.imag).toBeCloseTo(0, 10)
expect(PdagP.d.real).toBeCloseTo(1, 10)
expect(PdagP.d.imag).toBeCloseTo(0, 10)
})
})
describe('Rotation gates', () => {
describe('rx() - rotation around X', () => {
test('rx(0) is identity', () => {
const RX = gates.rx(0)
expect(RX.a.real).toBeCloseTo(1, 10)
expect(RX.a.imag).toBeCloseTo(0, 10)
expect(RX.b.real).toBeCloseTo(0, 10)
expect(RX.b.imag).toBeCloseTo(0, 10)
expect(RX.c.real).toBeCloseTo(0, 10)
expect(RX.c.imag).toBeCloseTo(0, 10)
expect(RX.d.real).toBeCloseTo(1, 10)
expect(RX.d.imag).toBeCloseTo(0, 10)
})
test('rx(π) is -iX', () => {
const RX = gates.rx(Math.PI)
// rx(π) = [[0, ±i], [±i, 0]] - sign depends on convention
expect(Math.abs(RX.a.real)).toBeCloseTo(0, 10)
expect(Math.abs(RX.a.imag)).toBeCloseTo(0, 10)
expect(Math.abs(RX.b.real)).toBeCloseTo(0, 10)
expect(Math.abs(RX.b.imag)).toBeCloseTo(1, 10)
expect(Math.abs(RX.c.real)).toBeCloseTo(0, 10)
expect(Math.abs(RX.c.imag)).toBeCloseTo(1, 10)
expect(Math.abs(RX.d.real)).toBeCloseTo(0, 10)
expect(Math.abs(RX.d.imag)).toBeCloseTo(0, 10)
})
test('rx is unitary', () => {
const RX = gates.rx(Math.PI / 4)
const RXdagRX = RX.conjugateTranspose().times(RX)
expect(RXdagRX.a.real).toBeCloseTo(1, 10)
expect(RXdagRX.a.imag).toBeCloseTo(0, 10)
expect(RXdagRX.b.real).toBeCloseTo(0, 10)
expect(RXdagRX.b.imag).toBeCloseTo(0, 10)
expect(RXdagRX.c.real).toBeCloseTo(0, 10)
expect(RXdagRX.c.imag).toBeCloseTo(0, 10)
expect(RXdagRX.d.real).toBeCloseTo(1, 10)
expect(RXdagRX.d.imag).toBeCloseTo(0, 10)
})
})
describe('ry() - rotation around Y', () => {
test('ry(0) is identity', () => {
const RY = gates.ry(0)
expect(RY.a.real).toBeCloseTo(1, 10)
expect(RY.a.imag).toBeCloseTo(0, 10)
expect(RY.b.real).toBeCloseTo(0, 10)
expect(RY.b.imag).toBeCloseTo(0, 10)
expect(RY.c.real).toBeCloseTo(0, 10)
expect(RY.c.imag).toBeCloseTo(0, 10)
expect(RY.d.real).toBeCloseTo(1, 10)
expect(RY.d.imag).toBeCloseTo(0, 10)
})
test('ry(π/2) creates superposition', () => {
const RY = gates.ry(Math.PI / 2)
const sqrt2 = 1 / Math.sqrt(2)
expect(RY.a.real).toBeCloseTo(sqrt2, 10)
expect(RY.a.imag).toBeCloseTo(0, 10)
expect(RY.b.real).toBeCloseTo(-sqrt2, 10)
expect(RY.b.imag).toBeCloseTo(0, 10)
expect(RY.c.real).toBeCloseTo(sqrt2, 10)
expect(RY.c.imag).toBeCloseTo(0, 10)
expect(RY.d.real).toBeCloseTo(sqrt2, 10)
expect(RY.d.imag).toBeCloseTo(0, 10)
})
test('ry is unitary', () => {
const RY = gates.ry(Math.PI / 3)
const RYdagRY = RY.conjugateTranspose().times(RY)
expect(RYdagRY.a.real).toBeCloseTo(1, 10)
expect(RYdagRY.a.imag).toBeCloseTo(0, 10)
expect(RYdagRY.b.real).toBeCloseTo(0, 10)
expect(RYdagRY.b.imag).toBeCloseTo(0, 10)
expect(RYdagRY.c.real).toBeCloseTo(0, 10)
expect(RYdagRY.c.imag).toBeCloseTo(0, 10)
expect(RYdagRY.d.real).toBeCloseTo(1, 10)
expect(RYdagRY.d.imag).toBeCloseTo(0, 10)
})
})
describe('rz() - rotation around Z', () => {
test('rz(0) is identity', () => {
const RZ = gates.rz(0)
expect(RZ.a.real).toBeCloseTo(1, 10)
expect(RZ.a.imag).toBeCloseTo(0, 10)
expect(RZ.b.real).toBeCloseTo(0, 10)
expect(RZ.b.imag).toBeCloseTo(0, 10)
expect(RZ.c.real).toBeCloseTo(0, 10)
expect(RZ.c.imag).toBeCloseTo(0, 10)
expect(RZ.d.real).toBeCloseTo(1, 10)
expect(RZ.d.imag).toBeCloseTo(0, 10)
})
test('rz is unitary', () => {
const RZ = gates.rz(Math.PI / 6)
const RZdagRZ = RZ.conjugateTranspose().times(RZ)
expect(RZdagRZ.a.real).toBeCloseTo(1, 10)
expect(RZdagRZ.a.imag).toBeCloseTo(0, 10)
expect(RZdagRZ.b.real).toBeCloseTo(0, 10)
expect(RZdagRZ.b.imag).toBeCloseTo(0, 10)
expect(RZdagRZ.c.real).toBeCloseTo(0, 10)
expect(RZdagRZ.c.imag).toBeCloseTo(0, 10)
expect(RZdagRZ.d.real).toBeCloseTo(1, 10)
expect(RZdagRZ.d.imag).toBeCloseTo(0, 10)
})
})
})
describe('Pauli matrices anticommutation', () => {
test('XY + YX = 0', () => {
const X = gates.x()
const Y = gates.y()
const XY = X.times(Y)
const YX = Y.times(X)
const sum = XY.plus(YX)
expect(sum.a.magnitude).toBeCloseTo(0, 10)
expect(sum.b.magnitude).toBeCloseTo(0, 10)
expect(sum.c.magnitude).toBeCloseTo(0, 10)
expect(sum.d.magnitude).toBeCloseTo(0, 10)
})
test('XZ + ZX = 0', () => {
const X = gates.x()
const Z = gates.z()
const XZ = X.times(Z)
const ZX = Z.times(X)
const sum = XZ.plus(ZX)
expect(sum.a.magnitude).toBeCloseTo(0, 10)
expect(sum.b.magnitude).toBeCloseTo(0, 10)
expect(sum.c.magnitude).toBeCloseTo(0, 10)
expect(sum.d.magnitude).toBeCloseTo(0, 10)
})
test('YZ + ZY = 0', () => {
const Y = gates.y()
const Z = gates.z()
const YZ = Y.times(Z)
const ZY = Z.times(Y)
const sum = YZ.plus(ZY)
expect(sum.a.magnitude).toBeCloseTo(0, 10)
expect(sum.b.magnitude).toBeCloseTo(0, 10)
expect(sum.c.magnitude).toBeCloseTo(0, 10)
expect(sum.d.magnitude).toBeCloseTo(0, 10)
})
})
describe('Special gate relations', () => {
test('HXH = Z', () => {
const H = gates.hadamard()
const X = gates.x()
const Z = gates.z()
const HXH = H.times(X).times(H)
expect(HXH.a.real).toBeCloseTo(Z.a.real, 10)
expect(HXH.a.imag).toBeCloseTo(Z.a.imag, 10)
expect(HXH.b.real).toBeCloseTo(Z.b.real, 10)
expect(HXH.b.imag).toBeCloseTo(Z.b.imag, 10)
expect(HXH.c.real).toBeCloseTo(Z.c.real, 10)
expect(HXH.c.imag).toBeCloseTo(Z.c.imag, 10)
expect(HXH.d.real).toBeCloseTo(Z.d.real, 10)
expect(HXH.d.imag).toBeCloseTo(Z.d.imag, 10)
})
test('HZH = X', () => {
const H = gates.hadamard()
const X = gates.x()
const Z = gates.z()
const HZH = H.times(Z).times(H)
expect(HZH.a.real).toBeCloseTo(X.a.real, 10)
expect(HZH.a.imag).toBeCloseTo(X.a.imag, 10)
expect(HZH.b.real).toBeCloseTo(X.b.real, 10)
expect(HZH.b.imag).toBeCloseTo(X.b.imag, 10)
expect(HZH.c.real).toBeCloseTo(X.c.real, 10)
expect(HZH.c.imag).toBeCloseTo(X.c.imag, 10)
expect(HZH.d.real).toBeCloseTo(X.d.real, 10)
expect(HZH.d.imag).toBeCloseTo(X.d.imag, 10)
})
})
})