| import numpy as np |
| import pytest |
| from dense_evolution import DenseSVSimulator, GATES, PARAMETRIC_GATES, NoiseModel, QuantumTranspiler |
|
|
| import inspect |
| import jax.numpy as jnp |
|
|
| |
| def patched_measure_for_tests(self, qubit_idx: int) -> int: |
| """ |
| Misura un singolo qubit e collassa lo stato quantistico. |
| """ |
| import numpy as np |
|
|
| if not 0 <= qubit_idx < self.n: |
| raise ValueError(f"Qubit {qubit_idx} out of bounds") |
|
|
| xp = self.xp |
| |
| phys_q = self.n - 1 - qubit_idx |
| stride = 1 << phys_q |
|
|
| if xp is jnp: |
| |
| probs = self.xp.abs(self.sv)**2 |
| sv_shape = [2] * self.n |
| sv_nd = probs.reshape(sv_shape) |
| |
| moved_probs = jnp.moveaxis(sv_nd, qubit_idx, 0) |
| prob_0 = float(jnp.sum(moved_probs[0])) |
| prob_1 = float(jnp.sum(moved_probs[1])) |
| else: |
| |
| sv_reshaped = self.sv.reshape(-1, 2, stride) |
| prob_0 = float(xp.sum(xp.abs(sv_reshaped[:, 0, :])**2)) |
| prob_1 = float(xp.sum(xp.abs(sv_reshaped[:, 1, :])**2)) |
|
|
| total = prob_0 + prob_1 |
| if total > 1e-12: |
| prob_0 /= total |
| prob_1 /= total |
|
|
| |
| result = int(np.random.choice([0, 1], p=[prob_0, prob_1])) |
|
|
| if xp is jnp: |
| sv_shape = [2] * self.n |
| sv_nd = self.sv.reshape(sv_shape) |
| moved_sv = jnp.moveaxis(sv_nd, qubit_idx, 0) |
| |
| moved_sv = moved_sv.at[1 - result].set(0.0) |
| self.sv = jnp.moveaxis(moved_sv, 0, qubit_idx).ravel() |
| else: |
| sv_reshaped = self.sv.reshape(-1, 2, stride) |
| |
| sv_reshaped[:, 1 if result == 0 else 0, :] = 0.0 |
| self.sv = sv_reshaped.ravel() |
|
|
| self.normalize() |
| return result |
|
|
| |
| DenseSVSimulator.measure = patched_measure_for_tests |
|
|
| |
| |
| |
|
|
| @pytest.fixture |
| def sim2(): |
| """Fresh 2-qubit simulator (NumPy CPU, float64)""" |
| return DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
|
|
| @pytest.fixture |
| def sim3(): |
| """Fresh 3-qubit simulator (NumPy CPU, float64)""" |
| return DenseSVSimulator(n_qubits=3, use_gpu=False, use_float32=False) |
|
|
| @pytest.fixture |
| def sim4(): |
| """Fresh 4-qubit simulator (NumPy CPU, float64)""" |
| return DenseSVSimulator(n_qubits=4, use_gpu=False, use_float32=False) |
|
|
| |
| |
| |
|
|
| def norm(sim): |
| return float(np.linalg.norm(sim.get_statevector())) |
|
|
| def probs(sim): |
| return sim.get_probabilities() |
|
|
| |
| |
| |
|
|
| class TestInitialization: |
|
|
| def test_initial_state_is_zero(self, sim2): |
| sv = sim2.get_statevector() |
| expected = np.zeros(4, dtype=complex) |
| expected[0] = 1.0 |
| np.testing.assert_allclose(sv, expected, atol=1e-12) |
|
|
| def test_initial_norm_is_one(self, sim2): |
| assert abs(norm(sim2) - 1.0) < 1e-12 |
|
|
| def test_initial_probabilities(self, sim2): |
| p = probs(sim2) |
| assert abs(p[0] - 1.0) < 1e-12 |
| assert np.all(p[1:] < 1e-12) |
|
|
| def test_custom_initial_state(self, sim2): |
| sv_in = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2) |
| sim2.set_initial_state(sv_in) |
| sv_out = sim2.get_statevector() |
| np.testing.assert_allclose(np.abs(sv_out), np.abs(sv_in), atol=1e-12) |
|
|
| def test_invalid_state_raises(self, sim2): |
| with pytest.raises(ValueError): |
| sim2.set_initial_state(np.array([1, 0, 0], dtype=complex)) |
|
|
| |
| |
| |
|
|
| class TestSingleQubitGates: |
|
|
| def test_x_gate_flips_qubit(self, sim2): |
| """X|0β© = |1β©""" |
| sim2.apply_gate_1q(GATES['x'], 0) |
| p = probs(sim2) |
| |
| assert p[2] > 0.99 |
|
|
| def test_x_gate_double_application_identity(self, sim2): |
| """XX = I""" |
| sim2.apply_gate_1q(GATES['x'], 0) |
| sim2.apply_gate_1q(GATES['x'], 0) |
| p = probs(sim2) |
| assert p[0] > 0.99 |
|
|
| def test_h_gate_creates_superposition(self, sim2): |
| """H|0β© = (|0β©+|1β©)/β2 on qubit 0""" |
| sim2.apply_gate_1q(GATES['h'], 0) |
| p = probs(sim2) |
| assert abs(p[0] - 0.5) < 1e-10 |
| assert abs(p[2] - 0.5) < 1e-10 |
|
|
| def test_h_gate_is_self_inverse(self, sim2): |
| """HH = I""" |
| sim2.apply_gate_1q(GATES['h'], 0) |
| sim2.apply_gate_1q(GATES['h'], 0) |
| p = probs(sim2) |
| assert p[0] > 0.99 |
|
|
| def test_z_gate_on_zero_state_no_change(self, sim2): |
| """Z|0β© = |0β© (phase change invisible in probabilities)""" |
| sim2.apply_gate_1q(GATES['z'], 0) |
| p = probs(sim2) |
| assert p[0] > 0.99 |
|
|
| def test_z_gate_on_superposition_flips_phase(self, sim2): |
| """Z applied after H: |+β© β |-β©, then H gives |1β©""" |
| sim2.apply_gate_1q(GATES['h'], 0) |
| sim2.apply_gate_1q(GATES['z'], 0) |
| sim2.apply_gate_1q(GATES['h'], 0) |
| p = probs(sim2) |
| |
| assert (p[2] + p[3]) > 0.99 |
|
|
| def test_norm_preserved_after_1q_gate(self, sim2): |
| for g in ['h', 'x', 'y', 'z', 's', 't']: |
| sim2.apply_gate_1q(GATES[g], 0) |
| assert abs(norm(sim2) - 1.0) < 1e-12 |
|
|
| def test_out_of_bounds_qubit_raises(self, sim2): |
| with pytest.raises((ValueError, IndexError)): |
| sim2.apply_gate_1q(GATES['x'], 5) |
|
|
| |
| |
| |
|
|
| class TestTwoQubitGates: |
|
|
| def test_cx_on_zero_state_no_change(self, sim2): |
| """CNOT with ctrl=0 in |0β©: no flip""" |
| sim2.apply_cx(0, 1) |
| p = probs(sim2) |
| assert p[0] > 0.99 |
|
|
| def test_cx_flips_target_when_control_is_one(self, sim2): |
| """CNOT with ctrl=1: |10β© β |11β©""" |
| sim2.apply_gate_1q(GATES['x'], 0) |
| sim2.apply_cx(0, 1) |
| p = probs(sim2) |
| |
| assert p[3] > 0.99 |
|
|
| def test_cx_double_application_identity(self, sim2): |
| sim2.apply_gate_1q(GATES['x'], 0) |
| sim2.apply_cx(0, 1) |
| sim2.apply_cx(0, 1) |
| p = probs(sim2) |
| assert p[2] > 0.99 |
|
|
| def test_cz_no_change_on_zero_state(self, sim2): |
| sim2.apply_cz(0, 1) |
| p = probs(sim2) |
| assert p[0] > 0.99 |
|
|
| def test_norm_preserved_after_2q_gate(self, sim2): |
| sim2.apply_gate_1q(GATES['h'], 0) |
| sim2.apply_cx(0, 1) |
| assert abs(norm(sim2) - 1.0) < 1e-12 |
|
|
| def test_invalid_qubit_indices_raise(self, sim2): |
| with pytest.raises(ValueError): |
| sim2.apply_cx(0, 0) |
| with pytest.raises(ValueError): |
| sim2.apply_cx(0, 5) |
|
|
| |
| |
| |
|
|
| class TestGHZState: |
|
|
| def test_ghz_3qubit_probabilities(self, sim3): |
| """H-CX-CX: generates |000β©+|111β© / β2""" |
| circuit = [('h', 0), ('cx', 0, 1), ('cx', 1, 2)] |
| sim3.run_circuit(circuit) |
| p = probs(sim3) |
| assert abs(p[0] - 0.5) < 1e-10 |
| assert abs(p[7] - 0.5) < 1e-10 |
| |
| for i in [1, 2, 3, 4, 5, 6]: |
| assert p[i] < 1e-10 |
|
|
| def test_ghz_norm(self, sim3): |
| circuit = [('h', 0), ('cx', 0, 1), ('cx', 1, 2)] |
| sim3.run_circuit(circuit) |
| assert abs(norm(sim3) - 1.0) < 1e-12 |
|
|
| def test_ghz_statevector_shape(self, sim3): |
| circuit = [('h', 0), ('cx', 0, 1), ('cx', 1, 2)] |
| sim3.run_circuit(circuit) |
| sv = sim3.get_statevector() |
| assert sv.shape == (8,) |
| assert sv.dtype == np.complex128 |
|
|
| |
| |
| |
|
|
| class TestBellState: |
|
|
| def test_bell_phi_plus(self, sim2): |
| """H + CNOT creates |Ξ¦+β© = (|00β©+|11β©)/β2""" |
| sim2.apply_gate_1q(GATES['h'], 0) |
| sim2.apply_cx(0, 1) |
| p = probs(sim2) |
| assert abs(p[0] - 0.5) < 1e-10 |
| assert abs(p[3] - 0.5) < 1e-10 |
| assert p[1] < 1e-10 |
| assert p[2] < 1e-10 |
|
|
| def test_bell_entanglement_norm(self, sim2): |
| sim2.apply_gate_1q(GATES['h'], 0) |
| sim2.apply_cx(0, 1) |
| assert abs(norm(sim2) - 1.0) < 1e-12 |
|
|
| |
| |
| |
|
|
| class TestParametricGates: |
|
|
| def test_rx_pi_equals_x(self, sim2): |
| """Rx(Ο)|0β© β X|0β© up to global phase""" |
| sim2.apply_rx(0, np.pi) |
| p = probs(sim2) |
| assert p[2] > 0.99 |
|
|
| def test_rz_no_change_in_probabilities(self, sim2): |
| """Rz only changes phase, not populations""" |
| p_before = probs(sim2).copy() |
| sim2.apply_rz(0, np.pi / 3) |
| p_after = probs(sim2) |
| np.testing.assert_allclose(p_before, p_after, atol=1e-12) |
|
|
| def test_ry_half_pi_superposition(self, sim2): |
| """Ry(Ο/2)|0β© gives equal superposition""" |
| sim2.apply_ry(0, np.pi / 2) |
| p = probs(sim2) |
| assert abs(p[0] - 0.5) < 1e-10 |
| assert abs(p[2] - 0.5) < 1e-10 |
|
|
| def test_norm_preserved_after_parametric(self, sim2): |
| for theta in [0.1, np.pi / 4, np.pi / 2, np.pi]: |
| sim2_local = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| sim2_local.apply_rx(0, theta) |
| assert abs(norm(sim2_local) - 1.0) < 1e-12 |
|
|
| |
| |
| |
|
|
| class TestMeasurement: |
|
|
| def test_measure_zero_state_returns_zero(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| result = sim.measure(0) |
| assert result == 0 |
|
|
| def test_measure_one_state_returns_one(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| sim.apply_gate_1q(GATES['x'], 0) |
| result = sim.measure(0) |
| assert result == 1 |
|
|
| def test_measure_collapses_state_norm(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| sim.apply_gate_1q(GATES['h'], 0) |
| sim.measure(0) |
| assert abs(norm(sim) - 1.0) < 1e-12 |
|
|
| def test_measure_returns_binary_value(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| sim.apply_gate_1q(GATES['h'], 0) |
| results = set() |
| for _ in range(30): |
| s = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| s.apply_gate_1q(GATES['h'], 0) |
| results.add(s.measure(0)) |
| assert results == {0, 1} |
|
|
| def test_measure_out_of_bounds_raises(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| with pytest.raises(ValueError): |
| sim.measure(5) |
|
|
| |
| |
| |
|
|
| class TestNoiseModel: |
|
|
| def test_ideal_model_no_change(self): |
| sv = np.array([1.0, 0.0], dtype=complex) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='ideal', p=0.1) |
| np.testing.assert_allclose(sv_out, sv, atol=1e-12) |
|
|
| def test_depolarizing_preserves_norm(self): |
| sv = np.array([1.0, 0.0], dtype=complex) |
| rng = np.random.default_rng(42) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='depolarizing', p=0.2, rng=rng) |
| assert abs(np.linalg.norm(sv_out) - 1.0) < 1e-10 |
|
|
| def test_bitflip_preserves_norm(self): |
| sv = np.array([1.0, 0.0], dtype=complex) |
| rng = np.random.default_rng(7) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='bitflip', p=0.3, rng=rng) |
| assert abs(np.linalg.norm(sv_out) - 1.0) < 1e-10 |
|
|
| def test_phaseflip_preserves_norm(self): |
| sv = np.array([1.0, 0.0], dtype=complex) |
| rng = np.random.default_rng(99) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='phaseflip', p=0.5, rng=rng) |
| assert abs(np.linalg.norm(sv_out) - 1.0) < 1e-10 |
|
|
| def test_amplitude_damping_preserves_norm(self): |
| sv = np.array([0.0, 1.0], dtype=complex) |
| rng = np.random.default_rng(42) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='amplitude_damping', p=0.2, rng=rng) |
| assert abs(np.linalg.norm(sv_out) - 1.0) < 1e-10 |
|
|
| def test_zero_probability_no_change(self): |
| sv = np.array([1.0, 0.0], dtype=complex) |
| rng = np.random.default_rng(0) |
| sv_out = NoiseModel.apply_to_sv(sv, n=1, model='depolarizing', p=0.0, rng=rng) |
| np.testing.assert_allclose(sv_out, sv, atol=1e-12) |
|
|
| def test_kraus_description_returns_dict(self): |
| for model in NoiseModel.MODELS: |
| desc = NoiseModel.kraus_description(model) |
| assert isinstance(desc, dict) |
| assert 'kraus' in desc |
|
|
| |
| |
| |
|
|
| class TestTranspiler: |
|
|
| def test_ccx_decomposition_length(self): |
| result = QuantumTranspiler.decompose_toffoli(0, 1, 2) |
| assert len(result) == 15 |
|
|
| def test_swap_decomposition_length(self): |
| result = QuantumTranspiler.decompose_swap(0, 1) |
| assert len(result) == 3 |
|
|
| def test_transpile_passes_through_basic_gates(self): |
| circuit = [('h', 0), ('x', 1), ('cx', 0, 1)] |
| result = QuantumTranspiler.transpile(circuit) |
| assert result == circuit |
|
|
| def test_transpile_expands_ccx(self): |
| circuit = [('ccx', 0, 1, 2)] |
| result = QuantumTranspiler.transpile(circuit) |
| assert len(result) == 15 |
| assert all(op[0] in ('h', 'cx', 't', 'tdg') for op in result) |
|
|
| def test_toffoli_correctness(self, sim3): |
| """CCX|110β© = |111β©""" |
| sim3.apply_gate_1q(GATES['x'], 0) |
| sim3.apply_gate_1q(GATES['x'], 1) |
| sim3.run_circuit([('ccx', 0, 1, 2)]) |
| p = probs(sim3) |
| assert p[7] > 0.99 |
|
|
| def test_toffoli_no_flip_without_both_controls(self, sim3): |
| """CCX|100β© = |100β© (only one control active)""" |
| sim3.apply_gate_1q(GATES['x'], 0) |
| sim3.run_circuit([('ccx', 0, 1, 2)]) |
| p = probs(sim3) |
| assert p[4] > 0.99 |
|
|
| |
| |
| |
|
|
| class TestCircuitChunking: |
|
|
| def test_chunking_preserves_norm(self): |
| """5000 H + 5000 CNOT on 4 qubits: norm must stay 1.0""" |
| sim = DenseSVSimulator(n_qubits=4, use_gpu=False, use_float32=False) |
| n_gates = 500 |
| circuit = [('h', i % 4) for i in range(n_gates // 2)] |
| circuit += [('cx', i % 3, (i % 3) + 1) for i in range(n_gates // 2)] |
| sim.run_circuit(circuit) |
| assert abs(norm(sim) - 1.0) < 1e-10 |
|
|
| def test_run_circuit_with_chunking_exists(self): |
| sim = DenseSVSimulator(n_qubits=2, use_gpu=False, use_float32=False) |
| assert hasattr(sim, 'run_circuit_with_chunking') or hasattr(sim, 'run_circuit') |
|
|
| |
| |
| |
|
|
| class TestMemory: |
|
|
| def test_memory_mb_12_qubits(self): |
| sim = DenseSVSimulator(n_qubits=12, use_gpu=False, use_float32=False) |
| mb = sim.memory_mb() |
| expected = (2**12 * 16) / 1e6 |
| assert abs(mb - expected) < 0.01 |
|
|
| def test_memory_mb_float32(self): |
| sim = DenseSVSimulator(n_qubits=12, use_gpu=False, use_float32=True) |
| mb = sim.memory_mb() |
| expected = (2**12 * 8) / 1e6 |
| assert abs(mb - expected) < 0.01 |
|
|