File size: 5,745 Bytes
2a87ede | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | """
Tests for dashboard_core.engine.run_circuit_from_qasm -- verifies the
real wiring (QASM -> dense_evolution's own QASMParser ->
dense_evolution.DenseSVSimulator) against known-exact circuits, not
against a mock.
Reference statevectors below are the textbook analytic results (Bell/GHZ
are symmetric under bit-order, so Qiskit's little-endian convention and
dense_evolution's native MSB-first convention agree here without needing
a Qiskit Statevector cross-check) -- no Qiskit involved in this file at
all, matching run_circuit_from_qasm itself never constructing a
qiskit.circuit.QuantumCircuit (see dashboard_core/engine.py's module
docstring for why that matters on macOS).
"""
import numpy as np
import pytest
from dashboard_core.engine import run_circuit_from_qasm, _to_qiskit_bit_order, _qiskit_bit_order_perm
_INV_SQRT2 = 1 / np.sqrt(2)
BELL_QASM = (
'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
'qreg q[2];\ncreg c[2];\n'
'h q[0];\ncx q[0],q[1];\n'
'measure q -> c;\n'
)
GHZ_QASM = (
'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
'qreg q[3];\ncreg c[3];\n'
'h q[0];\ncx q[0],q[1];\ncx q[1],q[2];\n'
'measure q -> c;\n'
)
def test_bell_state_statevector_matches_analytic_reference():
result = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1)
expected = np.array([_INV_SQRT2, 0, 0, _INV_SQRT2], dtype=complex)
assert np.allclose(result.statevector, expected, atol=1e-9)
def test_bell_state_probabilities_are_50_50_on_00_and_11():
result = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1)
assert result.probabilities[0] == pytest.approx(0.5, abs=1e-9)
assert result.probabilities[3] == pytest.approx(0.5, abs=1e-9)
assert result.probabilities[1] == pytest.approx(0.0, abs=1e-9)
assert result.probabilities[2] == pytest.approx(0.0, abs=1e-9)
def test_bell_state_counts_only_contain_00_and_11():
result = run_circuit_from_qasm(BELL_QASM, n_shots=500, seed=7)
assert sum(result.counts.values()) == 500
assert set(result.counts.keys()) <= {"00", "11"}
def test_ghz_state_matches_analytic_reference():
result = run_circuit_from_qasm(GHZ_QASM, n_shots=10, seed=3)
expected = np.zeros(8, dtype=complex)
expected[0] = _INV_SQRT2
expected[7] = _INV_SQRT2
assert np.allclose(result.statevector, expected, atol=1e-9)
assert result.n_qubits == 3
def test_seed_gives_reproducible_counts():
r1 = run_circuit_from_qasm(BELL_QASM, n_shots=200, seed=99)
r2 = run_circuit_from_qasm(BELL_QASM, n_shots=200, seed=99)
assert r1.counts == r2.counts
def test_zero_qubit_circuit_raises():
with pytest.raises(ValueError, match="at least 1 qubit"):
run_circuit_from_qasm(
'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[0];\ncreg c[0];\n', n_shots=10,
)
def test_ideal_run_has_no_fidelity_vs_ideal():
result = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1)
assert result.fidelity_vs_ideal is None
def test_zero_noise_probability_has_no_fidelity_vs_ideal():
result = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1, noise_model="depolarizing", noise_p=0.0)
assert result.fidelity_vs_ideal is None
def test_noisy_run_fidelity_vs_ideal_is_a_valid_probability():
result = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1, noise_model="depolarizing", noise_p=0.3)
assert 0.0 <= result.fidelity_vs_ideal <= 1.0 + 1e-9
def test_noisy_run_fidelity_vs_ideal_matches_direct_overlap_computation():
# Independent check, not just "some fidelity function ran": the ideal
# statevector doesn't depend on noise_p/rng at all, so a separate
# noise_model="ideal" call with the same seed must reproduce the exact
# pre-noise state the noisy run compared itself against -- then
# |<ideal|noisy>|^2 computed here from the two returned statevectors
# (same Qiskit bit-order convention, so a plain inner product is valid)
# must match what engine.py reported.
ideal = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1)
noisy = run_circuit_from_qasm(BELL_QASM, n_shots=10, seed=1, noise_model="depolarizing", noise_p=0.3)
expected = abs(np.vdot(ideal.statevector, noisy.statevector)) ** 2
assert noisy.fidelity_vs_ideal == pytest.approx(expected, abs=1e-9)
def _reference_qiskit_bit_order(values, n_qubits):
"""The pre-caching implementation, kept only as an independent
reference for the tests below."""
perm = [int(format(i, f'0{n_qubits}b')[::-1], 2) for i in range(2 ** n_qubits)]
return values[perm]
@pytest.mark.parametrize("n_qubits", [1, 2, 3, 4, 5, 8])
def test_to_qiskit_bit_order_matches_reference(n_qubits):
values = np.arange(2 ** n_qubits, dtype=complex)
np.testing.assert_array_equal(
_to_qiskit_bit_order(values, n_qubits),
_reference_qiskit_bit_order(values, n_qubits),
)
def test_qiskit_bit_order_perm_is_cached():
# BUG FIX (perf): _to_qiskit_bit_order used to rebuild the O(2**n)
# permutation from scratch on every call -- _qiskit_bit_order_perm
# caches it per n_qubits via lru_cache, so two calls at the same
# n_qubits must return the identical cached array object, not just
# an equal one.
perm_a = _qiskit_bit_order_perm(6)
perm_b = _qiskit_bit_order_perm(6)
assert perm_a is perm_b
def test_qiskit_bit_order_perm_is_read_only():
# Cached and shared across calls -- must not be mutable in place,
# or one caller corrupting it would corrupt every future call at
# that n_qubits.
perm = _qiskit_bit_order_perm(4)
with pytest.raises(ValueError):
perm[0] = 999
|