dense-Evolution / test_dashboard_core.py
Tatopenn's picture
Sync v8.1.41: pytest-cov coverage tracking, 84.1% -> 94.4%
9131ae9 verified
Raw
History Blame Contribute Delete
31.8 kB
"""
Smoke tests for dashboard_core.py — the compute/panel-builder layer behind
app_dashboard.py's Quantum Simulator tab. Mirrors the checks performed
manually while building the module: correct shapes/keys, no exceptions,
every panel builder returns a usable figure (including the empty-data
placeholder paths).
"""
import json
import matplotlib
matplotlib.use("Agg")
import numpy as np
import pandas as pd
import pytest
import dashboard_core as dc
from dashboard_core.qasm_library import _run_on_mps
from dashboard_core.simulation_runner import estrai_valore_puro
BELL_CIRCUIT = "Bell |Φ+⟩" # no parametric gates -> mock VQE path
VQE_CIRCUIT = "VQE ansatz Hâ‚‚" # has parametric gates -> real VQE path
@pytest.fixture(scope="module")
def bell_res():
return dc.run_simulation("Libreria Built-in", BELL_CIRCUIT, "", "ideal", 0.0, 128, 42, use_float32=True)
@pytest.fixture(scope="module")
def noisy_res():
return dc.run_simulation("Libreria Built-in", VQE_CIRCUIT, "", "depolarizing", 0.03, 128, 42, use_float32=True)
@pytest.fixture(scope="module")
def df_vqe_mock(bell_res):
return dc.run_vqe_telemetry(
bell_res["sim"], bell_res["parser"], dc.QASM_LIBRARY[BELL_CIRCUIT],
BELL_CIRCUIT, bell_res["n_qubits"], True, epochs=6, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
)
@pytest.fixture(scope="module")
def df_vqe_real(noisy_res):
return dc.run_vqe_telemetry(
noisy_res["sim"], noisy_res["parser"], dc.QASM_LIBRARY[VQE_CIRCUIT],
VQE_CIRCUIT, noisy_res["n_qubits"], True, epochs=4, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
)
@pytest.fixture(scope="module")
def md_telemetry():
return dc.run_md_telemetry(md_steps=20, md_temp=300)
# ── run_simulation ──────────────────────────────────────────────────────
def test_run_simulation_ideal_keys(bell_res):
expected_keys = {
"prob", "prob_ideal", "noise_factor", "fidelity", "n_qubits", "entropy",
"idx_max", "stato_dominante", "tempo", "ram", "nome", "porte_count",
"shots_data", "sim", "parser",
}
assert expected_keys <= set(bell_res.keys())
assert bell_res["n_qubits"] == 2
assert not np.isnan(bell_res["prob"]).any()
assert np.isclose(bell_res["prob"].sum(), 1.0, atol=1e-4)
def test_run_simulation_with_noise(noisy_res):
assert not np.isnan(noisy_res["prob"]).any()
assert 0.0 <= noisy_res["fidelity"] <= 1.0 + 1e-6
def test_run_simulation_noise_is_reproducible_from_seed():
# Regression guard: `seed` used to only reach np.random.seed() (which
# controls shot sampling), never NoiseModel.apply_to_sv's own
# randomness -- for a JAX statevector (the normal case here),
# apply_to_sv falls back to OS entropy unless `rng=`/`jax_key=` is
# passed explicitly, so two runs with the identical seed silently
# produced different noisy probability distributions. Found as a
# flaky test in test_mitigation_runner.py (same seed, different
# fidelity_per_scale on 2 of 3 runs) before being traced here.
kwargs = dict(source_mode="Libreria Built-in", circuit_name=BELL_CIRCUIT,
qasm_text="", noise_model="depolarizing", noise_p=0.2,
shots=64, seed=99, use_float32=True)
res_a = dc.run_simulation(**kwargs)
res_b = dc.run_simulation(**kwargs)
np.testing.assert_allclose(res_a["prob"], res_b["prob"])
assert res_a["fidelity"] == pytest.approx(res_b["fidelity"])
def test_run_simulation_heavy_circuit_15q():
res = dc.run_simulation("Libreria Built-in", "Error Mitigation (Real-Stress)", "", "bitflip", 0.05, 64, 42, use_float32=True)
assert res["n_qubits"] == 15
def test_bell_circuit_via_qasm_produces_real_entanglement(bell_res):
# Regression guard for a control/target swap bug found by audit: the QASM
# translation used to build (gate, target, control) instead of compiler.py's
# documented (gate, control, target) contract, so H+CX through the QASM
# path silently produced two separable qubits instead of a Bell state.
# Exercises the actual production path (parse -> run_circuit_jit_beast_mode),
# not the direct apply_cx API the rest of the test suite relies on.
# Reuses the module-scoped bell_res fixture rather than a fresh
# run_simulation call, so it doesn't flip the global jax_enable_x64 config
# away from what other tests in this module expect.
prob = bell_res["prob"]
assert np.isclose(prob[0], 0.5, atol=1e-8)
assert np.isclose(prob[3], 0.5, atol=1e-8)
assert prob[1] < 1e-8
assert prob[2] < 1e-8
# ── run_vqe_telemetry ───────────────────────────────────────────────────
def test_vqe_telemetry_mock_path(df_vqe_mock):
assert list(df_vqe_mock.columns) == ["VQE_Energy", "Entropy", "Purity", "Gradient", "Noise_Factor", "Theta_Correction"]
assert len(df_vqe_mock) == 6
def test_vqe_telemetry_real_path(df_vqe_real):
assert list(df_vqe_real.columns) == ["VQE_Energy", "Entropy", "Purity", "Gradient", "Noise_Factor", "Theta_Correction"]
assert len(df_vqe_real) == 4
def test_vqe_telemetry_heavy_qubit_guard_is_caller_responsibility():
# dashboard_core itself doesn't refuse heavy circuits (the UI layer
# gates this via QM_MM_HEAVY_QUBIT_THRESHOLD) — just confirm the
# threshold constant it's built around is exported and sane.
assert dc.QM_MM_HEAVY_QUBIT_THRESHOLD == 12
def test_vqe_gradient_matches_finite_difference():
# The real gradient (jax.grad through the engine dashboard_core now
# gets from dense_evolution.circuit_to_energy_fn) replaced a fake
# formula (0.5*(E-target)*sin(theta)+noise, no actual derivative
# anywhere). This is the correctness bar: compare against a
# finite-difference gradient computed by re-evaluating the energy
# function directly, on the dashboard's own QASM_LIBRARY circuit.
import jax
import jax.numpy as jnp
de = __import__("dense_evolution")
parser = de.QASMParser()
circ = parser.parse(dc.QASM_LIBRARY[VQE_CIRCUIT])
energy_fn, n_params = de.circuit_to_energy_fn(circ, circ.n_qubits)
assert n_params > 0
stato_zero = jnp.zeros(2 ** circ.n_qubits, dtype=jnp.complex128).at[0].set(1.0)
rng = np.random.default_rng(7)
h_matrix = jnp.diag(jnp.array(np.sort(rng.uniform(-2.5, 2.5, 2 ** circ.n_qubits))))
theta0 = rng.uniform(-np.pi, np.pi, n_params)
energy_and_grad = jax.jit(jax.value_and_grad(energy_fn, argnums=0, has_aux=True))
(_, _), grad = energy_and_grad(jnp.asarray(theta0), h_matrix, stato_zero)
eps = 1e-6
fd_grad = np.zeros(n_params)
for i in range(n_params):
tp, tm = theta0.copy(), theta0.copy()
tp[i] += eps
tm[i] -= eps
(ep, _), _ = energy_and_grad(jnp.asarray(tp), h_matrix, stato_zero)
(em, _), _ = energy_and_grad(jnp.asarray(tm), h_matrix, stato_zero)
fd_grad[i] = float((ep - em) / (2 * eps))
np.testing.assert_allclose(np.asarray(grad), fd_grad, atol=1e-6)
def test_vqe_energy_trends_downward_over_epochs():
# The old fake gradient (formula + injected Gaussian noise) had no
# principled reason to actually minimize the energy. A real gradient
# run through enough Adam steps on a fixed circuit/Hamiltonian should
# show real descent — this is what distinguishes "looks like VQE
# telemetry" from "is actually optimizing something".
sim = __import__("dense_evolution").DenseSVSimulator(n_qubits=2, use_float32=True)
parser = __import__("dense_evolution").QASMParser()
np.random.seed(123)
df = dc.run_vqe_telemetry(
sim, parser, dc.QASM_LIBRARY[VQE_CIRCUIT], VQE_CIRCUIT, 2, True,
epochs=40, lr=0.1, beta1=0.9, beta2=0.999, seed=123,
)
primi = df["VQE_Energy"].iloc[:5].mean()
ultimi = df["VQE_Energy"].iloc[-5:].mean()
assert ultimi < primi
# ── run_md_telemetry ────────────────────────────────────────────────────
def test_md_telemetry_shape(md_telemetry):
df_md, corr_matrix = md_telemetry
assert len(df_md) == 20
assert "Energia_VQE_Ha" in df_md.columns
assert corr_matrix.shape == (len(df_md.columns), len(df_md.columns))
def test_md_telemetry_mock_mode_labeled_honestly(md_telemetry):
# No hamiltonian_values/sv/n_qubits given (the md_telemetry fixture) --
# must fall back to the mock generator and say so via df.attrs, not
# present synthetic data as real.
df_md, _ = md_telemetry
assert df_md.attrs["is_real"] is False
assert "dimostrativi" in df_md.attrs["note"].lower()
def test_md_telemetry_real_mode_shape_and_attrs(bell_res):
# H2 (Idrogeno) - R = 0.74 Ã…: a real 2-qubit (dim=4) diagonal Hamiltonian
# matching BELL_CIRCUIT's 2-qubit statevector -- exercises the REAL
# branch (run_md_telemetry.py:34-37, _run_md_telemetry_real), never hit
# by the existing mock-only md_telemetry fixture.
h2_values = [-1.13, -0.45, 0.12, 0.64]
sv = bell_res["sim"].get_statevector()
df_md, corr_matrix = dc.run_md_telemetry(
md_steps=15, md_temp=300, hamiltonian_values=h2_values, sv=sv, n_qubits=2, seed=42)
assert len(df_md) == 15
assert df_md.attrs["is_real"] is True
assert "reale" in df_md.attrs["note"].lower()
expected_columns = {"Energia_VQE_Ha", "Entropia_von_Neumann_Bit",
"Purita_Stato", "Gradiente_Operatore", "Fattore_Rumore_Termico"}
assert expected_columns.issubset(set(df_md.columns))
assert corr_matrix.shape == (len(df_md.columns), len(df_md.columns))
def test_md_telemetry_real_mode_physically_sane_values(bell_res):
h2_values = [-1.13, -0.45, 0.12, 0.64]
sv = bell_res["sim"].get_statevector()
df_md, _ = dc.run_md_telemetry(
md_steps=10, md_temp=300, hamiltonian_values=h2_values, sv=sv, n_qubits=2, seed=7)
# Purity of a valid density matrix is always in (0, 1] (Tr(rho^2) <= 1,
# with equality only for a pure state; > 0 since rho is never the zero
# matrix).
assert (df_md["Purita_Stato"] > 0).all()
assert (df_md["Purita_Stato"] <= 1.0 + 1e-9).all()
# von Neumann entropy is non-negative by construction (-sum(p*log2(p))
# with p in [0,1]).
assert (df_md["Entropia_von_Neumann_Bit"] >= -1e-9).all()
# Energy is a probability-weighted average of the Hamiltonian's own
# eigenvalues, so it can never leave [min(H), max(H)].
assert df_md["Energia_VQE_Ha"].between(min(h2_values) - 1e-6, max(h2_values) + 1e-6).all()
# All finite -- no NaN/inf from a degenerate normalization edge case.
assert np.isfinite(df_md.to_numpy()).all()
def test_md_telemetry_real_mode_zero_temperature_skips_noise(bell_res):
# md_temp<=0 -> p_thermal=0 -> the noiseless branch (realizations=[psi],
# md_telemetry.py:166-167), a distinct code path from the noisy ENSEMBLE
# branch exercised by the other real-mode tests above.
h2_values = [-1.13, -0.45, 0.12, 0.64]
sv = bell_res["sim"].get_statevector()
df_md, _ = dc.run_md_telemetry(
md_steps=8, md_temp=0, hamiltonian_values=h2_values, sv=sv, n_qubits=2, seed=1)
assert df_md.attrs["is_real"] is True
assert (df_md["Fattore_Rumore_Termico"] == 0.0).all()
# No thermal noise -> the state stays pure under unitary evolution only
# -> purity stays at (numerically) exactly 1 every step.
assert np.allclose(df_md["Purita_Stato"], 1.0, atol=1e-9)
def test_md_telemetry_real_mode_reproducible_with_seed(bell_res):
h2_values = [-1.13, -0.45, 0.12, 0.64]
sv = bell_res["sim"].get_statevector()
df_a, _ = dc.run_md_telemetry(
md_steps=10, md_temp=300, hamiltonian_values=h2_values, sv=sv, n_qubits=2, seed=99)
df_b, _ = dc.run_md_telemetry(
md_steps=10, md_temp=300, hamiltonian_values=h2_values, sv=sv, n_qubits=2, seed=99)
pd.testing.assert_frame_equal(df_a, df_b)
def test_md_telemetry_dimension_mismatch_falls_back_to_mock(bell_res):
# hamiltonian_values given but wrong dimension for n_qubits=2 (needs 4,
# this has 8) -- exercises _check_real_mode_inputs's mismatch branch
# (md_telemetry.py:48-53), distinct from the "nothing given at all"
# path the mock-only fixture already covers.
sv = bell_res["sim"].get_statevector()
wrong_dim_hamiltonian = [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5] # dim=8, needs dim=4
df_md, _ = dc.run_md_telemetry(
md_steps=5, md_temp=300, hamiltonian_values=wrong_dim_hamiltonian, sv=sv, n_qubits=2, seed=0)
assert df_md.attrs["is_real"] is False
assert "non" in df_md.attrs["note"].lower() and "compatibile" in df_md.attrs["note"].lower()
# ── compute_overview_metrics ────────────────────────────────────────────
def test_compute_overview_metrics_keys(bell_res):
metrics = dc.compute_overview_metrics(bell_res, "ideal", 0.0)
expected_labels = {
"Qubits", "Hilbert Dim", "Gates", "Entropy", "Concurrence", "Purity",
"Spectral σ", "Top State", "P(top)", "RAM", "Time", "Noise", "Noise p",
}
assert {m["label"] for m in metrics} == expected_labels
assert all(isinstance(m["value"], str) for m in metrics)
# short values must never overflow a narrow st.metric tile — cap at a sane length
assert all(len(m["value"]) <= 20 for m in metrics)
def test_compute_overview_metrics_short_values_even_for_heavy_circuit():
# 15-qubit dominant-state bitstring used to blow past any reasonable
# tile width ("|000000000000000⟩") — must now stay short via the #idx form
res = dc.run_simulation("Libreria Built-in", "Error Mitigation (Real-Stress)", "", "ideal", 0.0, 64, 42, use_float32=True)
metrics = dc.compute_overview_metrics(res, "ideal", 0.0)
top_state = next(m for m in metrics if m["label"] == "Top State")
assert len(top_state["value"]) <= 10
assert "⟩" in top_state["help"]
# ── panel builders ──────────────────────────────────────────────────────
def test_build_panel_overview(bell_res, df_vqe_mock, md_telemetry):
df_md, corr_matrix = md_telemetry
fig = dc.build_panel_overview(bell_res, df_vqe_mock, corr_matrix, "ideal", 0.0)
assert fig is not None
def test_build_panel_fisica(bell_res):
fig = dc.build_panel_fisica(bell_res, seed=42)
assert fig is not None
def test_build_panel_mosaico(bell_res):
fig = dc.build_panel_mosaico(bell_res)
assert fig is not None
def test_build_panel_vqe_results_with_data(df_vqe_mock):
fig = dc.build_panel_vqe_results(df_vqe_mock)
assert fig is not None
def test_build_panel_vqe_results_empty():
fig = dc.build_panel_vqe_results(pd.DataFrame())
assert fig is not None
def test_build_panel_md_results_with_data(md_telemetry):
df_md, corr_matrix = md_telemetry
fig = dc.build_panel_md_results(df_md, corr_matrix)
assert fig is not None
def test_build_panel_md_results_empty():
fig = dc.build_panel_md_results(pd.DataFrame(), pd.DataFrame())
assert fig is not None
def test_build_panel_performance_with_history(bell_res):
history = [{"nome": bell_res["nome"], "n_qubits": bell_res["n_qubits"],
"tempo": bell_res["tempo"], "ram": bell_res["ram"]}]
fig = dc.build_panel_performance(bell_res, history)
assert fig is not None
def test_build_panel_performance_empty_history(bell_res):
fig = dc.build_panel_performance(bell_res, [])
assert fig is not None
def test_build_3d_helix_patch(bell_res):
fig = dc.build_3d_helix_patch(bell_res["n_qubits"], bell_res["prob"])
assert fig is not None
# ── benchmark + provenance export ───────────────────────────────────────
def test_run_benchmark_scan_small_range():
df = dc.run_benchmark_scan(range(2, 6, 2))
assert list(df.columns) == ["Qubits", "Hilbert_Dim", "Time_s", "RAM_Sim_MB", "Delta_RAM_RSS_MB"]
assert len(df) == 2
def test_build_provenance_json_roundtrip(bell_res):
history = [{"nome": bell_res["nome"], "n_qubits": bell_res["n_qubits"],
"tempo": bell_res["tempo"], "ram": bell_res["ram"],
"prob_sample": bell_res["prob"]}]
payload = dc.build_provenance_json(history)
parsed = json.loads(payload)
integrity_hash = parsed["metadata"]["integrity_sha256"]
assert len(integrity_hash) == 64 and all(c in "0123456789abcdef" for c in integrity_hash)
assert len(parsed["records"]) == 1
assert isinstance(parsed["records"][0]["prob_sample"], list)
# ── Hamiltonian library ─────────────────────────────────────────────────
def test_infer_qubit_count_from_qasm():
assert dc.infer_qubit_count_from_qasm(dc.QASM_LIBRARY[VQE_CIRCUIT]) == 2
assert dc.infer_qubit_count_from_qasm(dc.QASM_LIBRARY["Error Mitigation (Real-Stress)"]) == 15
assert dc.infer_qubit_count_from_qasm("") is None
assert dc.infer_qubit_count_from_qasm("garbage, no qreg here") is None
def test_get_compatible_hamiltonians():
compat_2q = dc.get_compatible_hamiltonians(2)
assert len(compat_2q) > 0
assert all(len(v) == 4 for v in compat_2q.values())
# the None-valued "Spettro Uniforme Classico" entry must never appear (matches dash.py's own filter)
assert all(v is not None for v in compat_2q.values())
assert dc.get_compatible_hamiltonians(0) == {}
assert dc.get_compatible_hamiltonians(None) == {}
def test_save_custom_hamiltonian_valid_and_duplicate():
library = dict(dc.LIBRERIA_HAMILTONIANE)
ok, _ = dc.save_custom_hamiltonian(library, "Test H", "[1.0, -1.0, 0.5, -0.5]")
assert ok and "Test H" in library
ok2, msg2 = dc.save_custom_hamiltonian(library, "Test H", "[1.0, -1.0, 0.5, -0.5]")
assert not ok2 and "esiste già" in msg2
def test_save_custom_hamiltonian_invalid_inputs():
library = dict(dc.LIBRERIA_HAMILTONIANE)
ok, _ = dc.save_custom_hamiltonian(library, "", "[1.0]")
assert not ok
ok, _ = dc.save_custom_hamiltonian(library, "Bad JSON", "not json")
assert not ok
ok, _ = dc.save_custom_hamiltonian(library, "Not a list", '{"a": 1}')
assert not ok
def test_build_panel_hamiltonian_with_and_without_data():
fig = dc.build_panel_hamiltonian([-1.13, -0.45, 0.12, 0.64], "H2 test")
assert fig is not None
fig_empty = dc.build_panel_hamiltonian(None, "none")
assert fig_empty is not None
def test_vqe_telemetry_with_custom_hamiltonian(noisy_res):
compat = dc.get_compatible_hamiltonians(noisy_res["n_qubits"])
values = next(iter(compat.values()))
df = dc.run_vqe_telemetry(
noisy_res["sim"], noisy_res["parser"], dc.QASM_LIBRARY[VQE_CIRCUIT],
VQE_CIRCUIT, noisy_res["n_qubits"], True, epochs=3, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
hamiltonian_values=values,
)
assert len(df) == 3
# ── on_epoch callback hook (purely additive) ────────────────────────────
def test_on_epoch_callback_mock_path(bell_res):
calls = []
df = dc.run_vqe_telemetry(
bell_res["sim"], bell_res["parser"], dc.QASM_LIBRARY[BELL_CIRCUIT],
BELL_CIRCUIT, bell_res["n_qubits"], True, epochs=5, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
on_epoch=lambda e, t, row: calls.append((e, t, row)),
)
assert len(calls) == 5
assert calls[0][0] == 0 and calls[0][1] == 5
assert calls[-1][0] == 4
assert set(calls[0][2].keys()) >= {"VQE_Energy", "Entropy", "Purity", "Gradient", "Noise_Factor", "Theta_Correction"}
assert len(df) == 5
def test_on_epoch_callback_real_path(noisy_res):
calls = []
df = dc.run_vqe_telemetry(
noisy_res["sim"], noisy_res["parser"], dc.QASM_LIBRARY[VQE_CIRCUIT],
VQE_CIRCUIT, noisy_res["n_qubits"], True, epochs=4, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
on_epoch=lambda e, t, row: calls.append((e, t, row)),
)
assert len(calls) == 4
assert calls[0][1] == 4
assert len(df) == 4
def test_run_vqe_telemetry_without_on_epoch_still_works(bell_res):
# regression guard: on_epoch defaults to None, existing callers unaffected
df = dc.run_vqe_telemetry(
bell_res["sim"], bell_res["parser"], dc.QASM_LIBRARY[BELL_CIRCUIT],
BELL_CIRCUIT, bell_res["n_qubits"], True, epochs=3, lr=0.05, beta1=0.9, beta2=0.999, seed=42,
)
assert len(df) == 3
# ── _run_on_mps gate dispatch ────────────────────────────────────────────
# comandi_beast_mode formats: 1q non-param [name, qubit, -1], 1q parametric
# [name, qubit, param], 2q non-param [name, control, target], 2q parametric
# [name, control, target, param], 3q [name, c1, c2, target] -- matches
# run_simulation's own comandi_beast_mode construction (simulation_runner.py:167-212).
# Called directly (not via run_simulation) because run_simulation's own
# gate-name whitelist there never forwards names _run_on_mps doesn't handle
# (e.g. 'ch', 'cswap') -- to reach _run_on_mps's own "unhandled gate" branch,
# it has to be exercised directly with a hand-built ops list.
def test_run_on_mps_cx_and_parametric_1q_gates():
# cx and the rx/ry/rz/u1/p parametric-1q branch -- not exercised by any
# of the gate-specific tests below, which deliberately pick other gates.
mps = _run_on_mps(
[["ry", 0, 0.7], ["rz", 1, 0.3], ["u1", 0, 0.1], ["p", 1, 0.2], ["cx", 0, 1]], n_qubits=2)
sv = np.asarray(mps.contract_to_statevector())
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
def test_run_on_mps_cz_and_swap_gates():
mps = _run_on_mps([["h", 0, -1], ["cz", 0, 1], ["swap", 1, 2]], n_qubits=3)
sv = np.asarray(mps.contract_to_statevector())
assert sv.shape == (8,)
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
def test_run_on_mps_cy_gate_matches_dense_simulator():
import dense_evolution as de
ops = [["h", 0, -1], ["cy", 0, 1]]
mps = _run_on_mps(ops, n_qubits=2)
mps_sv = np.asarray(mps.contract_to_statevector())
dense = de.DenseSVSimulator(n_qubits=2)
dense.run_circuit([("h", 0), ("cy", 0, 1)])
dense_sv = np.asarray(dense.get_statevector())
# global phase can differ -- compare via fidelity, not raw amplitudes
fidelity = abs(np.vdot(mps_sv, dense_sv)) ** 2
assert fidelity == pytest.approx(1.0, abs=1e-6)
def test_run_on_mps_cp_and_crz_parametric_2q_gates():
mps = _run_on_mps(
[["h", 0, -1], ["h", 1, -1], ["cp", 0, 1, 0.5], ["crz", 1, 2, 0.3]], n_qubits=3)
sv = np.asarray(mps.contract_to_statevector())
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
def test_run_on_mps_ccx_gate():
mps = _run_on_mps([["h", 0, -1], ["h", 1, -1], ["ccx", 0, 1, 2]], n_qubits=3)
sv = np.asarray(mps.contract_to_statevector())
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
def test_run_on_mps_u2_u3_skipped_with_warning(capsys):
# u2/u3 can't be represented in the single-param-slot comandi_beast_mode
# format -- _run_on_mps must skip them (not crash) and say so.
mps = _run_on_mps([["h", 0, -1], ["u2", 0, 0.1], ["u3", 0, 0.2]], n_qubits=1)
sv = np.asarray(mps.contract_to_statevector())
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
captured = capsys.readouterr()
assert "u2" in captured.out and "u3" in captured.out
assert "Warning" in captured.out
def test_run_on_mps_unhandled_gate_name_skipped_with_warning(capsys):
# A gate name outside _run_on_mps's own dispatch table (e.g. 'ch',
# 'cswap' -- both appear in QASM_LIBRARY circuits, but never reach here
# via run_simulation's own separate whitelist) must be skipped with a
# warning, not raise.
mps = _run_on_mps([["h", 0, -1], ["ch", 0, 1]], n_qubits=2)
sv = np.asarray(mps.contract_to_statevector())
assert np.isclose(np.linalg.norm(sv), 1.0, atol=1e-6)
captured = capsys.readouterr()
assert "ch" in captured.out and "Warning" in captured.out
# ── estrai_valore_puro ───────────────────────────────────────────────────
# Extracts a "pure" (int/float/str) value out of whatever shape the QASM
# parser's parsed circuit ops hand it (a plain int, a callable, an object
# exposing .index or .value, a numeric string) -- NOT Streamlit widget
# unwrapping, despite that being a plausible-sounding guess; this module
# has no Streamlit import at all. Almost none of its branches were
# exercised before -- existing tests only ever fed it already-plain ints.
def test_estrai_valore_puro_none_returns_zero():
assert estrai_valore_puro(None) == 0
def test_estrai_valore_puro_calls_function_like_callable():
assert estrai_valore_puro(lambda: 5) == 5
def test_estrai_valore_puro_function_like_callable_exception_returns_zero():
def boom():
raise RuntimeError("nope")
assert estrai_valore_puro(boom) == 0
def test_estrai_valore_puro_calls_generic_callable_object():
class Foo:
def __call__(self):
return 42
assert estrai_valore_puro(Foo()) == 42
def test_estrai_valore_puro_generic_callable_exception_falls_through():
class Boom:
def __call__(self):
raise RuntimeError("nope")
# No .index/.value, not a str/int/float -- exception in the generic
# callable branch just falls through to the final `return elemento`.
b = Boom()
assert estrai_valore_puro(b) is b
def test_estrai_valore_puro_index_attribute_plain_value():
class QubitRef:
def __init__(self, idx):
self.index = idx
assert estrai_valore_puro(QubitRef(3)) == 3
def test_estrai_valore_puro_index_attribute_callable_value():
class QubitRef:
def __init__(self, idx):
self.index = lambda: idx
assert estrai_valore_puro(QubitRef(7)) == 7
def test_estrai_valore_puro_value_attribute_plain_value():
class ParamRef:
def __init__(self, v):
self.value = v
assert estrai_valore_puro(ParamRef(0.5)) == 0.5
def test_estrai_valore_puro_value_attribute_callable_value():
class ParamRef:
def __init__(self, v):
self.value = lambda: v
assert estrai_valore_puro(ParamRef(0.25)) == 0.25
def test_estrai_valore_puro_string_with_dot_becomes_float():
assert estrai_valore_puro("3.5") == 3.5
def test_estrai_valore_puro_string_digits_become_int():
assert estrai_valore_puro("42") == 42
def test_estrai_valore_puro_non_numeric_string_returned_as_is():
assert estrai_valore_puro("abc") == "abc"
assert estrai_valore_puro("a.b") == "a.b" # has '.', still not a valid float
def test_estrai_valore_puro_plain_object_returned_unchanged():
class Opaque:
pass
o = Opaque()
assert estrai_valore_puro(o) is o
def test_estrai_valore_puro_numpy_scalar_types_pass_through():
assert estrai_valore_puro(np.int64(9)) == 9
assert estrai_valore_puro(np.float32(1.5)) == 1.5
# ── run_simulation: gates/engines not exercised by bell_res/noisy_res ──────
def test_run_simulation_ccx_gate_through_full_pipeline():
# Grover_3q_Oracle_111 uses ccx -- BELL_CIRCUIT/VQE_CIRCUIT don't,
# so the ccx/toffoli branch of _run_simulation_body's own
# comandi_beast_mode builder (simulation_runner.py:197-204) was
# never exercised through the real run_simulation entry point
# (only via _run_on_mps directly, a different code path).
res = dc.run_simulation("Libreria Built-in", "Grover_3q_Oracle_111", "", "ideal", 0.0, 64, 42, use_float32=True)
assert res["n_qubits"] == 3
assert np.isclose(np.sum(res["prob"]), 1.0, atol=1e-5)
def test_run_simulation_cp_gate_through_full_pipeline():
# QFT 4 qubit uses cp -- exercises the cp/crz branch
# (simulation_runner.py:205-212), also never hit via bell_res/noisy_res.
res = dc.run_simulation("Libreria Built-in", "QFT 4 qubit", "", "ideal", 0.0, 64, 42, use_float32=True)
assert res["n_qubits"] == 4
assert np.isclose(np.sum(res["prob"]), 1.0, atol=1e-5)
def test_run_simulation_mps_engine_with_noise():
# bell_res/noisy_res only ever combine engine='dense' with noise, or
# engine='mps' with noise_model='ideal' -- never engine='mps' PLUS a
# real noise channel, which is its own branch (the usa_mps sub-cases
# inside the noisy_model != 'ideal' path, simulation_runner.py:244-245
# and 255) computing sim_ideal and sim separately via _mps_statevector.
res = dc.run_simulation(
"Libreria Built-in", BELL_CIRCUIT, "", "depolarizing", 0.05, 64, 42,
use_float32=True, engine="mps")
assert res["n_qubits"] == 2
assert 0.0 <= res["fidelity"] <= 1.0 + 1e-6
assert np.isclose(np.sum(res["prob"]), 1.0, atol=1e-5)
def test_run_simulation_custom_qasm_text_mode():
# Every other run_simulation test in this file uses source_mode=
# "Libreria Built-in" -- the else branch (qasm_string=qasm_text,
# nome_circuito='Custom Workspace', simulation_runner.py:99-100),
# the actual "paste your own QASM" path in the dashboard, was untested.
custom_qasm = 'OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; creg c[2]; h q[0]; cx q[0],q[1]; measure q -> c;'
res = dc.run_simulation("Custom Workspace", "", custom_qasm, "ideal", 0.0, 64, 42, use_float32=True)
assert res["nome"] == "Custom Workspace"
assert res["n_qubits"] == 2
assert np.isclose(np.sum(res["prob"]), 1.0, atol=1e-5)
def test_run_simulation_mps_engine_rejects_over_24_qubits():
# simulation_runner.py:151-160's explicit ValueError -- MPSSimulator's
# dashboard integration is capped at 24 qubits (get_probabilities_sampled
# for larger circuits isn't wired to the dense-array-expecting panels
# yet). A qreg-only QASM string (no gates needed) is enough to report
# n_qubits=30 without actually running anything expensive.
big_qasm = 'OPENQASM 2.0; include "qelib1.inc"; qreg q[30]; creg c[30];'
with pytest.raises(ValueError, match="24 qubit"):
dc.run_simulation("Custom Workspace", "", big_qasm, "ideal", 0.0, 64, 42, use_float32=True, engine="mps")
def test_run_simulation_chunk_engine(monkeypatch):
# de.Chunk is only actually used (usa_chunk=True) when
# MemoryChunker(n_qubits).num_chunks > 1, which needs chunk_size_bits
# (get_dynamic_chunk's real-RAM-based result, always >= 16) to be
# smaller than n_qubits -- never true for the 2-4 qubit circuits used
# elsewhere in this file, and not worth a genuinely 17+ qubit circuit
# just to reach this branch quickly. Force it instead by patching
# get_dynamic_chunk to a tiny value so even a 3-qubit circuit chunks
# (de.Chunk's own correctness against DenseSVSimulator is already
# covered by test_dense_evolution.py; this only checks
# run_simulation's own dispatch actually uses de.Chunk when told to).
import dense_evolution.chunk as chunk_module
monkeypatch.setattr(chunk_module, "get_dynamic_chunk", lambda dtype_target: 1)
res = dc.run_simulation("Libreria Built-in", BELL_CIRCUIT, "", "ideal", 0.0, 64, 42, use_float32=True)
from dense_evolution import Chunk
assert isinstance(res["sim"], Chunk)
assert np.isclose(np.sum(res["prob"]), 1.0, atol=1e-5)