| """ |
| Molecular-dynamics-style telemetry. |
| |
| Two modes, chosen automatically by run_md_telemetry() based on what the |
| caller passes in: |
| |
| REAL mode -- a valid Hamiltonian (from LIBRERIA_HAMILTONIANE or a custom |
| one, diagonal, length 2**n_qubits -- see hamiltonians.py) |
| and the circuit's current statevector are both given, and |
| their sizes agree. The state is evolved in real time under |
| that Hamiltonian and a physically-grounded thermal-noise |
| channel; every column is a real, computed quantity. |
| |
| MOCK mode -- no compatible Hamiltonian/statevector given. Falls back to |
| the original synthetic generator (adapted verbatim from |
| run_md_simulation_dummy, dash.py:1152 -- explicitly a |
| placeholder in the source since it was first written, never |
| real MD/quantum-chemistry data). |
| |
| Both modes return the same (df_md, corr_matrix) shape the rest of the |
| dashboard already expects. Which mode actually ran is recorded in |
| df_md.attrs['is_real'] / df_md.attrs['note'] (pandas' own metadata slot -- |
| survives on the DataFrame itself, does not require changing every call |
| site's return-value unpacking) so the UI can label the result honestly |
| instead of presenting a mock run as if it were real. |
| """ |
|
|
| import numpy as np |
| import pandas as pd |
|
|
|
|
| def run_md_telemetry(md_steps, md_temp, hamiltonian_values=None, sv=None, n_qubits=None, seed=None): |
| is_real, mismatch_note = _check_real_mode_inputs(hamiltonian_values, sv, n_qubits) |
| if is_real: |
| df_md, corr_matrix = _run_md_telemetry_real(md_steps, md_temp, hamiltonian_values, sv, int(n_qubits), seed) |
| df_md.attrs['is_real'] = True |
| df_md.attrs['note'] = 'Dinamica reale: evoluzione temporale sotto l\'Hamiltoniana selezionata.' |
| else: |
| df_md, corr_matrix = _run_md_telemetry_mock(md_steps, md_temp) |
| df_md.attrs['is_real'] = False |
| df_md.attrs['note'] = mismatch_note |
| return df_md, corr_matrix |
|
|
|
|
| def _check_real_mode_inputs(hamiltonian_values, sv, n_qubits): |
| if hamiltonian_values is None or sv is None or n_qubits is None: |
| return False, 'Dati dimostrativi -- nessuna Hamiltoniana reale attiva.' |
| expected_dim = 2 ** int(n_qubits) |
| if len(hamiltonian_values) != expected_dim or len(sv) != expected_dim: |
| return False, ( |
| f'Dati dimostrativi -- l\'Hamiltoniana selezionata (dim={len(hamiltonian_values)}) ' |
| f'non e\' compatibile con questo circuito (dim richiesta={expected_dim}).' |
| ) |
| return True, None |
|
|
|
|
| def _run_md_telemetry_mock(md_steps, md_temp): |
| """Synthetic MD telemetry generator — adapted verbatim from run_md_simulation_dummy |
| (dash.py:1144), explicitly a placeholder for real MD/quantum-chemistry calculations |
| in the source itself, not physically simulated data.""" |
| data = { |
| "Step": [], "Energia_VQE_Ha": [], "Entropia_von_Neumann_Bit": [], |
| "Purita_Stato": [], "ID_Operatore_ADAPT": [], "Gradiente_Operatore": [], |
| "Fattore_Rumore_Termico": [], "Correzione_Variazionale_Theta": [], "Gradiente_Base_Classica": [] |
| } |
|
|
| temp_factor = md_temp / 300.0 if md_temp > 0 else 0.1 |
| temp_factor = np.clip(temp_factor, 0.1, 2.0) |
|
|
| for step in range(md_steps): |
| data["Step"].append(step) |
|
|
| energy = -25.0 * np.exp(-step / (md_steps / 5.0)) * temp_factor + np.random.uniform(-0.5, 0.5) |
| data["Energia_VQE_Ha"].append(energy) |
|
|
| entropy = 0.5 + 0.5 * (step / md_steps) * temp_factor + np.random.uniform(-0.01, 0.01) |
| data["Entropia_von_Neumann_Bit"].append(entropy) |
|
|
| purity = 0.8 * np.exp(-step / (md_steps / 10.0)) / temp_factor + np.random.uniform(-0.005, 0.005) |
| data["Purita_Stato"].append(purity) |
|
|
| data["ID_Operatore_ADAPT"].append(np.random.randint(0, 3)) |
| grad = 1.5 * np.exp(-step / (md_steps / 2.0)) * temp_factor + np.random.uniform(-0.05, 0.05) |
| data["Gradiente_Operatore"].append(grad) |
| data["Fattore_Rumore_Termico"].append(1.0 - (step / md_steps * 0.1) * temp_factor + np.random.uniform(-0.001, 0.001)) |
| data["Correzione_Variazionale_Theta"].append(0.1 * np.sin(step * 0.01 * temp_factor) + np.random.uniform(-0.005, 0.005)) |
| data["Gradiente_Base_Classica"].append(grad * 0.8) |
|
|
| df_md = pd.DataFrame(data) |
| df_md.set_index("Step", inplace=True) |
| corr_matrix = df_md.corr(method="pearson") |
| return df_md, corr_matrix |
|
|
|
|
| def _run_md_telemetry_real(md_steps, md_temp, hamiltonian_values, sv, n_qubits, seed): |
| """Real-time evolution of the actual circuit state under the actual |
| (diagonal) Hamiltonian selected by the user, plus a physically-grounded |
| thermal-noise channel reusing dense_evolution's own NoiseModel (not a |
| reinvented noise formula). |
| |
| Every LIBRERIA_HAMILTONIANE entry is a diagonal Hamiltonian (a flat list |
| of 2**n_qubits eigenvalues, see hamiltonians.py) -- so time evolution |
| under it is *exact*, not a Trotter approximation: each basis-state |
| amplitude just picks up a phase exp(-i * eigenvalue * dt) per step. |
| |
| DT is a fixed, unitless step size (these Hamiltonians are unitless |
| benchmark energies, not calibrated to a real physical time unit -- there |
| is no "correct" DT to derive, this one is chosen so md_steps=20-500 |
| sweeps a visually reasonable range of phase evolution, nothing more). |
| |
| "Temperature" drives the probability of dense_evolution's own |
| NoiseModel.apply_to_sv (depolarizing channel) applied once per step -- |
| real decoherence from real, already-tested code, not a fabricated |
| thermal-noise curve. |
| |
| Purity is computed exactly (not approximated) from an ensemble-averaged |
| density matrix: ENSEMBLE independent noisy realizations of the same |
| step are drawn, their outer products averaged into rho_avg, and |
| Purita_Stato = Tr(rho_avg^2). This is exact given rho_avg; the only |
| approximation is that ENSEMBLE=8 samples estimate the true |
| noise-channel-averaged state (more samples would converge tighter). |
| Every LIBRERIA_HAMILTONIANE entry is <=6 qubits (dim<=64), so |
| materializing dim x dim density matrices per step is cheap. |
| """ |
| import jax.numpy as jnp |
| import dense_evolution as de |
|
|
| DT = 0.15 |
| ENSEMBLE = 8 |
| dim = 2 ** n_qubits |
|
|
| h_diag = np.asarray(hamiltonian_values, dtype=np.float64) |
| psi = np.asarray(sv, dtype=np.complex128).copy() |
| norm = np.linalg.norm(psi) |
| if norm > 1e-12: |
| psi = psi / norm |
|
|
| temp_factor = float(np.clip(md_temp / 300.0 if md_temp > 0 else 0.1, 0.1, 2.0)) |
| |
| |
| |
| p_thermal = float(np.clip((temp_factor - 0.1) / 1.9 * 0.30, 0.0, 0.30)) |
|
|
| rng = np.random.default_rng(seed if seed is not None else 0) |
|
|
| data = { |
| "Step": [], "Energia_VQE_Ha": [], "Entropia_von_Neumann_Bit": [], |
| "Purita_Stato": [], "Gradiente_Operatore": [], "Fattore_Rumore_Termico": [], |
| } |
|
|
| energy_prev = None |
| for step in range(md_steps): |
| phase = np.exp(-1j * h_diag * DT) |
| psi = psi * phase |
| psi = psi / np.linalg.norm(psi) |
|
|
| if p_thermal > 0: |
| realizations = [] |
| for _ in range(ENSEMBLE): |
| sub_seed = int(rng.integers(0, 2**31 - 1)) |
| noisy = de.NoiseModel.apply_to_sv( |
| sv=np.array(psi, copy=True), n=n_qubits, model='depolarizing', |
| p=p_thermal, rng=np.random.default_rng(sub_seed), |
| ) |
| realizations.append(np.asarray(noisy, dtype=np.complex128)) |
| else: |
| realizations = [psi] |
|
|
| rho_avg = sum(np.outer(v, v.conj()) for v in realizations) / len(realizations) |
| prob_t = np.real(np.diag(rho_avg)) |
| purity_t = float(np.real(np.trace(rho_avg @ rho_avg))) |
|
|
| |
| |
| |
| |
| eigvals = np.linalg.eigvalsh(rho_avg) |
| eig_safe = eigvals[eigvals > 1e-12] |
| entropy_t = float(-np.sum(eig_safe * np.log2(eig_safe))) if len(eig_safe) else 0.0 |
|
|
| energy_t = float(np.sum(prob_t * h_diag)) |
| drift_t = 0.0 if energy_prev is None else abs(energy_t - energy_prev) |
| energy_prev = energy_t |
|
|
| data["Step"].append(step) |
| data["Energia_VQE_Ha"].append(energy_t) |
| data["Entropia_von_Neumann_Bit"].append(entropy_t) |
| data["Purita_Stato"].append(purity_t) |
| data["Gradiente_Operatore"].append(drift_t) |
| data["Fattore_Rumore_Termico"].append(p_thermal) |
|
|
| |
| |
| |
| psi = realizations[-1] / np.linalg.norm(realizations[-1]) |
|
|
| df_md = pd.DataFrame(data) |
| df_md.set_index("Step", inplace=True) |
| corr_matrix = df_md.corr(method="pearson") |
| return df_md, corr_matrix |
|
|