Files
native-ai-quantum-energy/tests/test_quantum_simulator.py
2025-11-10 23:28:54 -06:00

197 lines
5.9 KiB
Python

import math
import random
import pytest
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
def assert_complex_approx(value: complex, expected_real: float, expected_imag: float = 0.0) -> None:
assert value.real == pytest.approx(expected_real)
assert value.imag == pytest.approx(expected_imag)
def test_hadamard_creates_superposition():
qc = QuantumCircuit(1)
qc.apply_hadamard(0)
state = qc.statevector()
assert_complex_approx(state[0], 1 / math.sqrt(2))
assert_complex_approx(state[1], 1 / math.sqrt(2))
def test_pauli_x_flips_qubit():
qc = QuantumCircuit(1)
qc.apply_pauli_x(0)
state = qc.statevector()
assert_complex_approx(state[0], 0.0)
assert_complex_approx(state[1], 1.0)
def test_pauli_y_adds_phase_to_one_state():
qc = QuantumCircuit(1)
qc.apply_pauli_y(0)
state = qc.statevector()
assert_complex_approx(state[0], 0.0)
assert_complex_approx(state[1], 0.0, 1.0)
def test_pauli_z_flips_phase_of_one_state():
qc = QuantumCircuit(1)
qc.apply_pauli_x(0)
qc.apply_pauli_z(0)
state = qc.statevector()
assert_complex_approx(state[0], 0.0)
assert_complex_approx(state[1], -1.0)
def test_phase_gates_apply_expected_phases():
qc = QuantumCircuit(1)
qc.apply_pauli_x(0)
qc.apply_s(0)
state = qc.statevector()
assert_complex_approx(state[1], 0.0, 1.0)
qc.apply_t(0)
state = qc.statevector()
expected = math.cos(math.pi / 4)
expected_imag = math.sin(math.pi / 4)
assert_complex_approx(state[1], -expected_imag, expected)
def test_cnot_entangles_qubits():
qc = QuantumCircuit(2)
qc.apply_hadamard(0)
qc.apply_cnot(0, 1)
probs = qc.probabilities()
assert probs[0] == pytest.approx(0.5, rel=1e-6)
assert probs[3] == pytest.approx(0.5, rel=1e-6)
assert probs[1] == pytest.approx(0.0, abs=1e-12)
assert probs[2] == pytest.approx(0.0, abs=1e-12)
def test_controlled_z_adds_phase_to_11_state():
qc = QuantumCircuit(2)
qc.apply_hadamard(0)
qc.apply_hadamard(1)
qc.apply_cz(0, 1)
state = qc.statevector()
amplitude_11 = state[3]
assert amplitude_11.real == pytest.approx(-0.5, rel=1e-6)
assert amplitude_11.imag == pytest.approx(0.0, abs=1e-12)
def test_measure_collapses_state(monkeypatch):
qc = QuantumCircuit(1)
qc.apply_hadamard(0)
monkeypatch.setattr(random, "random", lambda: 0.25)
outcome = qc.measure(0)
assert outcome == 1
state = qc.statevector()
assert_complex_approx(state[0], 0.0)
assert_complex_approx(state[1], 1.0)
def test_invalid_qubit_index():
qc = QuantumCircuit(1)
with pytest.raises(IndexError):
qc.apply_hadamard(2)
def test_cnot_requires_distinct_qubits():
qc = QuantumCircuit(2)
with pytest.raises(ValueError):
qc.apply_cnot(0, 0)
def test_rotation_gates_match_expected_unitaries():
qc = QuantumCircuit(1)
initial = [1 / math.sqrt(2), 1 / math.sqrt(2)]
qc.initialize_statevector(initial)
angle = math.pi / 3
qc.apply_rx(0, angle)
cos = math.cos(angle / 2)
sin = math.sin(angle / 2)
expected_rx = [
cos * initial[0] - 1j * sin * initial[1],
-1j * sin * initial[0] + cos * initial[1],
]
for actual, expected in zip(qc.statevector(), expected_rx):
assert actual == pytest.approx(expected)
qc.initialize_statevector(initial)
qc.apply_ry(0, angle)
expected_ry = [
math.cos(angle / 2) * initial[0] - math.sin(angle / 2) * initial[1],
math.sin(angle / 2) * initial[0] + math.cos(angle / 2) * initial[1],
]
for actual, expected in zip(qc.statevector(), expected_ry):
assert actual == pytest.approx(expected)
qc.initialize_statevector(initial)
qc.apply_rz(0, angle)
phase_neg = math.cos(angle / 2) - 1j * math.sin(angle / 2)
phase_pos = math.cos(angle / 2) + 1j * math.sin(angle / 2)
expected_rz = [phase_neg * initial[0], phase_pos * initial[1]]
for actual, expected in zip(qc.statevector(), expected_rz):
assert actual == pytest.approx(expected)
def test_rotation_identity_cases():
qc = QuantumCircuit(1)
qc.apply_rx(0, 0.0)
assert qc.statevector()[0] == pytest.approx(1.0)
qc.apply_ry(0, 0.0)
assert qc.statevector()[0] == pytest.approx(1.0)
qc.apply_rz(0, 0.0)
assert qc.probabilities() == pytest.approx([1.0, 0.0])
qc.apply_hadamard(0)
before = qc.probabilities()
qc.apply_rx(0, 2 * math.pi)
qc.apply_ry(0, 2 * math.pi)
qc.apply_rz(0, 2 * math.pi)
after = qc.probabilities()
assert after == pytest.approx(before)
def test_measure_subset_collapses_only_requested_qubits(monkeypatch):
qc = QuantumCircuit(2)
qc.apply_hadamard(0)
monkeypatch.setattr(random, "random", lambda: 0.2)
outcome = qc.measure_subset([1])
assert outcome == "0"
state = qc.statevector()
assert state[0] == pytest.approx(1 / math.sqrt(2))
assert state[2] == pytest.approx(1 / math.sqrt(2))
assert state[1] == pytest.approx(0.0)
assert state[3] == pytest.approx(0.0)
def test_measure_subset_on_entangled_pair(monkeypatch):
qc = QuantumCircuit(2)
qc.apply_hadamard(0)
qc.apply_cnot(0, 1)
monkeypatch.setattr(random, "random", lambda: 0.25)
outcome = qc.measure_subset([0])
assert outcome == "1"
state = qc.statevector()
assert state[3] == pytest.approx(1.0 + 0j)
for idx in (0, 1, 2):
assert state[idx] == pytest.approx(0.0 + 0j)
def test_initialize_statevector_validates_input():
qc = QuantumCircuit(2)
with pytest.raises(ValueError):
qc.initialize_statevector([1.0, 0.0])
with pytest.raises(ValueError):
qc.initialize_statevector([0.5, 0.5, 0.5, 0.2])
amplitudes = [0.5 + 0j, 0.5j, -0.5 + 0j, 0.5 - 0.5j]
norm = math.sqrt(sum(abs(a) ** 2 for a in amplitudes))
amplitudes = [a / norm for a in amplitudes]
qc.initialize_statevector(amplitudes)
assert qc.statevector() == pytest.approx(amplitudes)