@qbead/bloch-sphere
Version:
A 3D Bloch Sphere visualisation built with Three.js and TypeScript.
437 lines (382 loc) • 14.2 kB
text/typescript
import { describe, expect, test } from 'bun:test'
import { BlochVector } from './bloch-vector'
import { Complex } from './complex'
import { Vector3 } from 'three'
import * as gates from './gates'
describe('BlochVector', () => {
describe('Static quantum state constructors', () => {
test('ZERO represents |0> state (north pole)', () => {
const zero = BlochVector.ZERO
expect(zero.x).toBe(0)
expect(zero.y).toBe(0)
expect(zero.z).toBe(1)
})
test('ONE represents |1> state (south pole)', () => {
const one = BlochVector.ONE
expect(one.x).toBe(0)
expect(one.y).toBe(0)
expect(one.z).toBe(-1)
})
test('PLUS represents |+> state', () => {
const plus = BlochVector.PLUS
expect(plus.x).toBe(1)
expect(plus.y).toBe(0)
expect(plus.z).toBe(0)
})
test('MINUS represents |-> state', () => {
const minus = BlochVector.MINUS
expect(minus.x).toBe(-1)
expect(minus.y).toBe(0)
expect(minus.z).toBe(0)
})
test('I represents |i> state', () => {
const i = BlochVector.I
expect(i.x).toBe(0)
expect(i.y).toBe(1)
expect(i.z).toBe(0)
})
test('MINUS_I represents |-i> state', () => {
const minusI = BlochVector.MINUS_I
expect(minusI.x).toBe(0)
expect(minusI.y).toBe(-1)
expect(minusI.z).toBe(0)
})
test('random() creates vector on Bloch sphere surface', () => {
const v = BlochVector.random()
expect(v.amplitude).toBeCloseTo(1, 10)
})
test('zero() creates zero state', () => {
const z = BlochVector.zero()
expect(z.x).toBe(0)
expect(z.y).toBe(0)
expect(z.z).toBe(1)
})
})
describe('from() constructor', () => {
test('from(x, y, z) creates vector - overload 1', () => {
const v = BlochVector.from(1, 0, 0)
expect(v.x).toBe(1)
expect(v.y).toBe(0)
expect(v.z).toBe(0)
expect(v).toBeInstanceOf(BlochVector)
})
test('from(x, y, z) with all coordinates - overload 1', () => {
const v = BlochVector.from(0.5, 0.6, 0.7)
expect(v.x).toBe(0.5)
expect(v.y).toBe(0.6)
expect(v.z).toBe(0.7)
})
test('from([x, y, z]) creates vector from array - overload 3', () => {
const v = BlochVector.from([0, 1, 0])
expect(v.x).toBe(0)
expect(v.y).toBe(1)
expect(v.z).toBe(0)
expect(v).toBeInstanceOf(BlochVector)
})
test('from(Vector3) creates vector from three.js Vector3 - overload 2', () => {
const vec3 = new Vector3(0, 0, 1)
const v = BlochVector.from(vec3)
expect(v.x).toBe(0)
expect(v.y).toBe(0)
expect(v.z).toBe(1)
expect(v).toBeInstanceOf(BlochVector)
expect(v).not.toBe(vec3)
})
test('from(Vector3) with non-unit vector - overload 2', () => {
const vec3 = new Vector3(2, 3, 4)
const v = BlochVector.from(vec3)
expect(v.x).toBe(2)
expect(v.y).toBe(3)
expect(v.z).toBe(4)
})
test('from(BlochVector) clones vector - overload 4', () => {
const original = new BlochVector(1, 2, 3)
const clone = BlochVector.from(original)
expect(clone.x).toBe(1)
expect(clone.y).toBe(2)
expect(clone.z).toBe(3)
expect(clone).toBeInstanceOf(BlochVector)
expect(clone).not.toBe(original)
})
test('from(BlochVector) preserves quantum state - overload 4', () => {
const original = BlochVector.PLUS
const clone = BlochVector.from(original)
expect(clone.x).toBeCloseTo(original.x, 10)
expect(clone.y).toBeCloseTo(original.y, 10)
expect(clone.z).toBeCloseTo(original.z, 10)
})
})
describe('fromAngles()', () => {
test('fromAngles(0, 0) creates |0> state', () => {
const v = BlochVector.fromAngles(0, 0)
expect(v.x).toBeCloseTo(0, 10)
expect(v.y).toBeCloseTo(0, 10)
expect(v.z).toBeCloseTo(1, 10)
})
test('fromAngles(π, 0) creates |1> state', () => {
const v = BlochVector.fromAngles(Math.PI, 0)
expect(v.x).toBeCloseTo(0, 10)
expect(v.y).toBeCloseTo(0, 10)
expect(v.z).toBeCloseTo(-1, 10)
})
test('fromAngles(π/2, 0) creates |+> state', () => {
const v = BlochVector.fromAngles(Math.PI / 2, 0)
expect(v.x).toBeCloseTo(1, 10)
expect(v.y).toBeCloseTo(0, 10)
expect(v.z).toBeCloseTo(0, 10)
})
test('fromAngles(π/2, π/2) creates |i> state', () => {
const v = BlochVector.fromAngles(Math.PI / 2, Math.PI / 2)
expect(v.x).toBeCloseTo(0, 10)
expect(v.y).toBeCloseTo(1, 10)
expect(v.z).toBeCloseTo(0, 10)
})
test('fromAngles creates unit vector', () => {
const v = BlochVector.fromAngles(Math.PI / 3, Math.PI / 4)
expect(v.amplitude).toBeCloseTo(1, 10)
})
})
describe('Properties', () => {
test('angles for |0> are (0, 0)', () => {
const v = BlochVector.ZERO
expect(v.theta).toBeCloseTo(0, 10)
expect(v.phi).toBeCloseTo(0, 10)
})
test('angles for |1> are (π, 0)', () => {
const v = BlochVector.ONE
expect(v.theta).toBeCloseTo(Math.PI, 10)
expect(v.phi).toBeCloseTo(0, 10)
})
test('angles for |+> are (π/2, 0)', () => {
const v = BlochVector.PLUS
expect(v.theta).toBeCloseTo(Math.PI / 2, 10)
expect(v.phi).toBeCloseTo(0, 10)
})
test('angles for |-> are (π/2, π)', () => {
const v = BlochVector.MINUS
expect(v.theta).toBeCloseTo(Math.PI / 2, 10)
expect(v.phi).toBeCloseTo(Math.PI, 10)
})
test('angles for |i> are (π/2, π/2)', () => {
const v = BlochVector.I
expect(v.theta).toBeCloseTo(Math.PI / 2, 10)
expect(v.phi).toBeCloseTo(Math.PI / 2, 10)
})
test('angles for |-i> are (π/2, 3π/2)', () => {
const v = BlochVector.MINUS_I
expect(v.theta).toBeCloseTo(Math.PI / 2, 10)
expect(v.phi).toBeCloseTo(3 * Math.PI / 2, 10)
})
test('amplitude for unit vector is 1', () => {
const v = BlochVector.PLUS
expect(v.amplitude).toBe(1)
})
test('amplitude for scaled vector', () => {
const v = new BlochVector(3, 4, 0)
expect(v.amplitude).toBe(5)
})
})
describe('angles() and setAngles()', () => {
test('angles() returns [theta, phi]', () => {
const v = BlochVector.PLUS
const [theta, phi] = v.angles()
expect(theta).toBeCloseTo(Math.PI / 2, 10)
expect(phi).toBeCloseTo(0, 10)
})
test('setAngles() modifies vector in place', () => {
const v = BlochVector.zero()
v.setAngles([Math.PI / 2, 0])
expect(v.x).toBeCloseTo(1, 10)
expect(v.y).toBeCloseTo(0, 10)
expect(v.z).toBeCloseTo(0, 10)
})
test('setAngles() returns this for chaining', () => {
const v = BlochVector.zero()
const result = v.setAngles([Math.PI / 2, 0])
expect(result).toBe(v)
})
test('round trip through angles', () => {
const original = new BlochVector(0.5, 0.6, 0.62)
const normalized = original.clone().normalize() // Normalize first
const angles = normalized.angles()
const reconstructed = BlochVector.zero().setAngles(angles)
expect(reconstructed.x).toBeCloseTo(normalized.x, 8)
expect(reconstructed.y).toBeCloseTo(normalized.y, 8)
expect(reconstructed.z).toBeCloseTo(normalized.z, 8)
})
})
describe('Density matrix', () => {
test('densityMatrix() for |0> state', () => {
const v = BlochVector.ZERO
const rho = v.densityMatrix()
// |0><0| = [[1, 0], [0, 0]]
expect(rho[0][0].real).toBeCloseTo(1, 10)
expect(rho[0][0].imag).toBeCloseTo(0, 10)
expect(rho[0][1].real).toBeCloseTo(0, 10)
expect(rho[0][1].imag).toBeCloseTo(0, 10)
expect(rho[1][0].real).toBeCloseTo(0, 10)
expect(rho[1][0].imag).toBeCloseTo(0, 10)
expect(rho[1][1].real).toBeCloseTo(0, 10)
expect(rho[1][1].imag).toBeCloseTo(0, 10)
})
test('densityMatrix() for |1> state', () => {
const v = BlochVector.ONE
const rho = v.densityMatrix()
// |1><1| = [[0, 0], [0, 1]]
expect(rho[0][0].real).toBeCloseTo(0, 10)
expect(rho[0][0].imag).toBeCloseTo(0, 10)
expect(rho[1][1].real).toBeCloseTo(1, 10)
expect(rho[1][1].imag).toBeCloseTo(0, 10)
})
test('densityMatrix() for |+> state', () => {
const v = BlochVector.PLUS
const rho = v.densityMatrix()
// |+><+| = 0.5 * [[1, 1], [1, 1]]
expect(rho[0][0].real).toBeCloseTo(0.5, 10)
expect(rho[0][1].real).toBeCloseTo(0.5, 10)
expect(rho[1][0].real).toBeCloseTo(0.5, 10)
expect(rho[1][1].real).toBeCloseTo(0.5, 10)
})
test('rho is alias for densityMatrix()', () => {
const v = BlochVector.PLUS
const rho1 = v.densityMatrix()
const rho2 = v.rho
expect(rho1[0][0].real).toBe(rho2[0][0].real)
expect(rho1[0][0].imag).toBe(rho2[0][0].imag)
})
test('density matrix is Hermitian', () => {
const v = BlochVector.random()
const rho = v.densityMatrix()
// ρ† = ρ (Hermitian)
expect(rho[0][1].real).toBeCloseTo(rho[1][0].real, 10)
expect(rho[0][1].imag).toBeCloseTo(-rho[1][0].imag, 10)
})
test('density matrix has trace 1', () => {
const v = BlochVector.random()
const rho = v.densityMatrix()
const trace = rho[0][0].real + rho[1][1].real
expect(trace).toBeCloseTo(1, 10)
})
})
describe('fromDensityMatrix()', () => {
test('round trip through density matrix for |0>', () => {
const original = BlochVector.ZERO
const rho = original.densityMatrix()
const reconstructed = BlochVector.fromDensityMatrix(rho)
expect(reconstructed.x).toBeCloseTo(original.x, 10)
expect(reconstructed.y).toBeCloseTo(original.y, 10)
expect(reconstructed.z).toBeCloseTo(original.z, 10)
})
test('round trip through density matrix for |+>', () => {
const original = BlochVector.PLUS
const rho = original.densityMatrix()
const reconstructed = BlochVector.fromDensityMatrix(rho)
expect(reconstructed.x).toBeCloseTo(original.x, 10)
expect(reconstructed.y).toBeCloseTo(original.y, 10)
expect(reconstructed.z).toBeCloseTo(original.z, 10)
})
test('round trip through density matrix for random state', () => {
const original = BlochVector.random()
const rho = original.densityMatrix()
const reconstructed = BlochVector.fromDensityMatrix(rho)
expect(reconstructed.x).toBeCloseTo(original.x, 10)
expect(reconstructed.y).toBeCloseTo(original.y, 10)
expect(reconstructed.z).toBeCloseTo(original.z, 10)
})
})
describe('applyOperator()', () => {
test('Hadamard on |0> gives |+>', () => {
const zero = BlochVector.ZERO
const result = zero.applyOperator(gates.hadamard())
expect(result.x).toBeCloseTo(1, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(0, 10)
})
test('Hadamard on |1> gives |->', () => {
const one = BlochVector.ONE
const result = one.applyOperator(gates.hadamard())
expect(result.x).toBeCloseTo(-1, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(0, 10)
})
test('X gate flips |0> to |1>', () => {
const zero = BlochVector.ZERO
const result = zero.applyOperator(gates.x())
expect(result.x).toBeCloseTo(0, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(-1, 10)
})
test('X gate flips |1> to |0>', () => {
const one = BlochVector.ONE
const result = one.applyOperator(gates.x())
expect(result.x).toBeCloseTo(0, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(1, 10)
})
test('Z gate leaves |0> unchanged', () => {
const zero = BlochVector.ZERO
const result = zero.applyOperator(gates.z())
expect(result.x).toBeCloseTo(0, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(1, 10)
})
test('Z gate flips |+> to |->', () => {
const plus = BlochVector.PLUS
const result = plus.applyOperator(gates.z())
expect(result.x).toBeCloseTo(-1, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(0, 10)
})
test('Y gate on |0> gives i|1>', () => {
const zero = BlochVector.ZERO
const result = zero.applyOperator(gates.y())
// Y|0> = i|1>, which on Bloch sphere is -|1>
expect(result.x).toBeCloseTo(0, 10)
expect(result.y).toBeCloseTo(0, 10)
expect(result.z).toBeCloseTo(-1, 10)
})
test('applying operator preserves norm', () => {
const v = BlochVector.random()
const result = v.applyOperator(gates.hadamard())
expect(result.amplitude).toBeCloseTo(1, 10)
})
})
describe('slerpTo()', () => {
test('slerpTo() with t=0 returns start vector', () => {
const start = BlochVector.ZERO
const end = BlochVector.ONE
const result = start.slerpTo(end, 0)
expect(result.x).toBeCloseTo(start.x, 10)
expect(result.y).toBeCloseTo(start.y, 10)
expect(result.z).toBeCloseTo(start.z, 10)
})
test('slerpTo() with t=1 returns end vector', () => {
const start = BlochVector.ZERO
const end = BlochVector.ONE
const result = start.slerpTo(end, 1)
expect(result.x).toBeCloseTo(end.x, 10)
expect(result.y).toBeCloseTo(end.y, 10)
expect(result.z).toBeCloseTo(end.z, 10)
})
test('slerpTo() with t=0.5 gives midpoint on sphere', () => {
const start = BlochVector.ZERO
const end = BlochVector.ONE
const result = start.slerpTo(end, 0.5)
expect(result.amplitude).toBeCloseTo(1, 10)
// Midpoint between north and south pole
expect(result.z).toBeCloseTo(0, 10)
})
test('slerpTo() preserves unit length', () => {
const start = BlochVector.random()
const end = BlochVector.random()
const result = start.slerpTo(end, 0.5)
expect(result.amplitude).toBeCloseTo(1, 10)
})
})
describe('toString()', () => {
test('formats vector correctly', () => {
const v = new BlochVector(1, 2, 3)
expect(v.toString()).toBe('(1, 2, 3)')
})
})
})