File size: 19,980 Bytes
d2486ac | 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | """
Interactive ipywidgets panel โ Colab/Jupyter-native, no Streamlit, no
external tunnel/link. Lives entirely in the calling cell's output.
Full feature parity with ui_pages/quantum_simulator.py's Streamlit page
(same 9 output panels, same sidebar controls: circuit source, engine,
noise, ZNE/predictive healing, VQE, custom Hamiltonian, MD) -- built on
the same dashboard_core functions that page calls, just driven by
ipywidgets instead of Streamlit's rerun-the-whole-script model.
Requires the `ipywidgets` extra (`pip install dense-evolution[dashboard]`).
Not a port of the old legacy/dash.py ipywidgets panel, which predates
this package's refactor, the ZNE feature, and several bug fixes found
while building it (see README changelog v8.1.27-8.1.32).
"""
import pandas as pd
from .qasm_library import QASM_LIBRARY, infer_qubit_count_from_qasm
from .hamiltonians import LIBRERIA_HAMILTONIANE, get_compatible_hamiltonians, save_custom_hamiltonian
from .simulation_runner import run_simulation
from .vqe_engine import QM_MM_HEAVY_QUBIT_THRESHOLD, run_vqe_telemetry
from .md_telemetry import run_md_telemetry
from .metrics import compute_overview_metrics
from .panels import (
build_panel_overview, build_panel_fisica, build_panel_mosaico,
build_panel_vqe_results, build_panel_md_results, build_panel_performance,
build_panel_hamiltonian,
)
from .helix_3d import build_3d_helix_patch
from .mitigation_runner import run_mitigation_sweep
from .mitigation_panel import build_panel_mitigation
_NEUTRAL_AI_META = {'fallback_triggered': False, 'adaptive_radius_used': 0, 'reconstruction_error': 0.0}
def _heal_telemetry(df):
"""Same logic as ui_pages/ai_middleware.py::heal_telemetry, duplicated
(not imported) deliberately: ui_pages is repo-only, not part of the
installable package `dashboard_core` ships in, so this module can't
depend on it without breaking a bare `pip install dense-evolution`."""
from ia_utils.vector_healing import enhanced_dense_healing_hybrid
if df is None or df.empty:
return (df if df is not None else pd.DataFrame()), dict(_NEUTRAL_AI_META)
healed_values, metadata = enhanced_dense_healing_hybrid(df.to_numpy(dtype=float))
healed_df = pd.DataFrame(healed_values, columns=df.columns, index=df.index)
return healed_df, metadata
def launch_interactive_panel():
"""Builds and displays the full interactive panel (sidebar-equivalent
controls + a 9-tab output area, same panels as the Streamlit
dashboard) in the current Jupyter/Colab cell output. Call directly
after import:
import dashboard_core as dc
dc.launch_interactive_panel()
Returns the outer ipywidgets.VBox (already displayed)."""
try:
import ipywidgets as widgets
from IPython.display import display, clear_output
except ImportError as e:
raise ImportError(
"launch_interactive_panel() requires ipywidgets and IPython "
"(Jupyter/Colab environment). Install with: "
"pip install dense-evolution[dashboard]"
) from e
import matplotlib.pyplot as plt
run_history = []
ham_library = dict(LIBRERIA_HAMILTONIANE)
# โโ Circuit source โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_source_mode = widgets.RadioButtons(
options=['Libreria Built-in', 'Custom QASM Textarea'], value='Libreria Built-in',
description='Sorgente:', style={'description_width': 'initial'},
)
w_circuit = widgets.Dropdown(
options=list(QASM_LIBRARY.keys()), value='Bell |ฮฆ+โฉ', description='Circuito:',
style={'description_width': 'initial'}, layout=widgets.Layout(width='420px'),
)
w_qasm_text = widgets.Textarea(
value='OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; creg c[2]; h q[0]; cx q[0],q[1]; measure q -> c;',
description='OpenQASM 2.0:', style={'description_width': 'initial'},
layout=widgets.Layout(width='600px', height='90px'),
)
w_qasm_text.layout.display = 'none'
def _on_source_mode_change(change):
libreria = change['new'] == 'Libreria Built-in'
w_circuit.layout.display = None if libreria else 'none'
w_qasm_text.layout.display = 'none' if libreria else None
w_source_mode.observe(_on_source_mode_change, names='value')
# โโ Engine / noise โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_engine = widgets.Dropdown(options=['dense', 'mps'], value='dense', description='Motore:')
w_noise_model = widgets.Dropdown(
options=['ideal', 'depolarizing', 'bitflip', 'phaseflip', 'amplitude_damping', 'combined'],
value='ideal', description='Rumore:',
)
w_noise_p = widgets.FloatSlider(value=0.0, min=0.0, max=0.5, step=0.01, description='p:')
w_shots = widgets.IntSlider(value=512, min=50, max=5000, step=50, description='Shots:')
w_seed = widgets.IntText(value=42, description='Seed:')
w_double_precision = widgets.Checkbox(value=False, description='Doppia precisione (float64)')
# โโ ZNE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_zne_enabled = widgets.Checkbox(value=False, description='Abilita Zero-Noise Extrapolation')
w_zne_healing = widgets.Checkbox(value=False, description='Healing predittivo (ฮpre_emp-adapted)')
w_zne_target_sigma = widgets.FloatText(value=10.0, description='Target ฯ ideale:',
style={'description_width': 'initial'})
# โโ VQE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_vqe_enabled = widgets.Checkbox(value=True, description='Abilita telemetria VQE')
w_vqe_epochs = widgets.IntSlider(value=20, min=5, max=100, description='Epochs:')
w_vqe_lr = widgets.FloatLogSlider(value=0.05, min=-3, max=-0.3, description='Learning rate:',
style={'description_width': 'initial'})
w_vqe_beta1 = widgets.FloatSlider(value=0.9, min=0.5, max=0.999, step=0.001, description='Adam ฮฒ1:')
w_vqe_beta2 = widgets.FloatSlider(value=0.999, min=0.9, max=0.9999, step=0.0001, description='Adam ฮฒ2:')
w_confirm_heavy_vqe = widgets.Checkbox(
value=False,
description=f'Confermo VQE reale anche su circuiti pesanti (>{QM_MM_HEAVY_QUBIT_THRESHOLD} qubit)',
style={'description_width': 'initial'},
)
# โโ Custom Hamiltonian โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_ham_enabled = widgets.Checkbox(value=False, description='Abilita Hamiltoniana personalizzata')
w_ham_mode = widgets.RadioButtons(options=['Libreria Built-in', 'Custom JSON Textarea'],
value='Libreria Built-in', description='Modalitร :')
w_ham_select = widgets.Dropdown(options=[], description='Hamiltoniana:',
style={'description_width': 'initial'}, layout=widgets.Layout(width='500px'))
w_ham_json = widgets.Textarea(value='[-1.13, -0.45, 0.12, 0.64]', description='Array JSON:',
style={'description_width': 'initial'})
w_ham_save_name = widgets.Text(value='', description='Nome (salva):',
style={'description_width': 'initial'})
w_ham_save_btn = widgets.Button(description='๐พ Salva in libreria')
w_ham_status = widgets.HTML(value='')
w_ham_box = widgets.VBox([]) # populated/hidden based on w_ham_enabled
def _current_qasm():
return QASM_LIBRARY[w_circuit.value] if w_source_mode.value == 'Libreria Built-in' else w_qasm_text.value
def _refresh_ham_options(*_):
n_qubits = infer_qubit_count_from_qasm(_current_qasm())
compatible = get_compatible_hamiltonians(n_qubits, ham_library)
w_ham_select.options = list(compatible.keys())
w_ham_status.value = (
'' if compatible else
f'<span style="color:#ff6b35">Nessuna Hamiltoniana compatibile con {n_qubits or "?"} qubit โ usa Custom JSON.</span>'
)
def _on_ham_save_clicked(_btn):
ok, msg = save_custom_hamiltonian(ham_library, w_ham_save_name.value, w_ham_json.value)
w_ham_status.value = f'<span style="color:{"#00ff9d" if ok else "#ff6b35"}">{msg}</span>'
if ok:
_refresh_ham_options()
w_ham_save_btn.on_click(_on_ham_save_clicked)
w_circuit.observe(_refresh_ham_options, names='value')
w_qasm_text.observe(_refresh_ham_options, names='value')
w_source_mode.observe(_refresh_ham_options, names='value')
def _on_ham_enabled_change(change):
if not change['new']:
w_ham_box.children = []
return
_refresh_ham_options()
w_ham_box.children = [w_ham_mode, w_ham_select, w_ham_json, w_ham_save_name, w_ham_save_btn, w_ham_status]
def _on_ham_mode_change(change):
libreria = change['new'] == 'Libreria Built-in'
w_ham_select.layout.display = None if libreria else 'none'
for w in (w_ham_json, w_ham_save_name, w_ham_save_btn):
w.layout.display = 'none' if libreria else None
w_ham_enabled.observe(_on_ham_enabled_change, names='value')
w_ham_mode.observe(_on_ham_mode_change, names='value')
# โโ MD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_md_enabled = widgets.Checkbox(value=True, description='Abilita telemetria MD')
w_md_steps = widgets.IntSlider(value=80, min=10, max=500, description='MD steps:')
w_md_temp = widgets.IntSlider(value=300, min=10, max=800, description='Temperatura (K):')
# โโ Run control โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
w_run = widgets.Button(description='โถ Esegui Simulazione', button_style='primary')
w_status = widgets.HTML(value='')
# โโ Output: 9 tabs, matching the Streamlit page exactly โโโโโโโโโโ
tab_names = ['Overview', 'Fisica Stato', 'Mosaico', 'VQE Results', 'MD Results',
'Performance', '3D Helix', 'Hamiltonian', 'Mitigation (ZNE)']
tab_outputs = [widgets.Output() for _ in tab_names]
tabs = widgets.Tab(children=tab_outputs)
for i, name in enumerate(tab_names):
tabs.set_title(i, name)
def _show(idx, *figs):
with tab_outputs[idx]:
clear_output(wait=True)
for fig in figs:
if fig is None:
continue
display(fig)
if hasattr(fig, 'savefig'): # matplotlib Figure, not plotly
plt.close(fig)
def _on_run_clicked(_btn):
w_run.disabled = True
w_status.value = '<span style="color:#00c8ff">โณ Esecuzione circuito...</span>'
try:
res = run_simulation(
w_source_mode.value, w_circuit.value, w_qasm_text.value,
w_noise_model.value, w_noise_p.value, w_shots.value, int(w_seed.value),
use_float32=not w_double_precision.value, engine=w_engine.value,
)
except Exception as e:
w_status.value = f'<span style="color:#ff6b35">Errore durante l\'esecuzione del circuito: {e}</span>'
w_run.disabled = False
return
mitigation_res = None
if w_zne_enabled.value:
w_status.value = '<span style="color:#00c8ff">โณ Mitigazione ZNE...</span>'
if w_noise_model.value == 'ideal':
w_status.value = '<span style="color:#ff6b35">ZNE richiede un modello di rumore attivo, non \'ideal\' โ saltato.</span>'
else:
try:
mitigation_res = run_mitigation_sweep(
w_source_mode.value, w_circuit.value, w_qasm_text.value, w_noise_model.value,
w_noise_p.value, w_shots.value, int(w_seed.value),
use_float32=not w_double_precision.value, engine=w_engine.value,
healing_enabled=w_zne_healing.value, target_sigma_ideal=w_zne_target_sigma.value,
)
except Exception as e:
w_status.value = f'<span style="color:#ff6b35">Errore durante la mitigazione ZNE: {e}</span>'
# Computed once, reused by VQE, MD and the Hamiltonian panel below --
# was three separate copies of the same extraction before.
hamiltonian_values = None
if w_ham_enabled.value:
if w_ham_mode.value == 'Libreria Built-in' and w_ham_select.value:
hamiltonian_values = ham_library.get(w_ham_select.value)
elif w_ham_mode.value == 'Custom JSON Textarea':
import json
try:
hamiltonian_values = json.loads(w_ham_json.value)
except json.JSONDecodeError:
hamiltonian_values = None
df_vqe = pd.DataFrame()
vqe_ai_meta = dict(_NEUTRAL_AI_META)
if w_vqe_enabled.value:
if res['n_qubits'] > QM_MM_HEAVY_QUBIT_THRESHOLD and not w_confirm_heavy_vqe.value:
w_status.value = (
f'<span style="color:#ff6b35">Circuito a {res["n_qubits"]} qubit: telemetria VQE saltata '
f'(spunta "Confermo VQE reale..." per eseguirla comunque).</span>'
)
else:
w_status.value = '<span style="color:#00c8ff">โณ VQE...</span>'
try:
df_vqe = run_vqe_telemetry(
res['sim'], res['parser'], _current_qasm(), w_circuit.value, res['n_qubits'],
not w_double_precision.value, w_vqe_epochs.value, w_vqe_lr.value,
w_vqe_beta1.value, w_vqe_beta2.value, int(w_seed.value),
hamiltonian_values=hamiltonian_values,
)
except Exception as e:
w_status.value = f'<span style="color:#ff6b35">Errore durante la telemetria VQE: {e}</span>'
if not df_vqe.empty:
df_vqe, vqe_ai_meta = _heal_telemetry(df_vqe)
df_md, corr_matrix = pd.DataFrame(), pd.DataFrame()
md_ai_meta = dict(_NEUTRAL_AI_META)
md_is_real, md_note = False, ''
if w_md_enabled.value:
w_status.value = '<span style="color:#00c8ff">โณ Telemetria MD...</span>'
# Real dynamics under hamiltonian_values when it's compatible with
# this circuit's statevector; otherwise run_md_telemetry falls
# back to the labeled mock itself (see md_telemetry.py) -- no
# branching needed here.
df_md, _raw_corr = run_md_telemetry(
w_md_steps.value, w_md_temp.value,
hamiltonian_values=hamiltonian_values, sv=res['sim'].sv,
n_qubits=res['n_qubits'], seed=int(w_seed.value),
)
md_is_real = df_md.attrs.get('is_real', False)
md_note = df_md.attrs.get('note', '')
df_md, md_ai_meta = _heal_telemetry(df_md)
corr_matrix = df_md.corr(method='pearson')
run_history.append({
'nome': res['nome'], 'n_qubits': res['n_qubits'], 'tempo': res['tempo'], 'ram': res['ram'],
'porte_count': res['porte_count'], 'entropy': float(res['entropy']), 'fidelity': res['fidelity'],
'stato_dominante': res['stato_dominante'], 'noise_model': w_noise_model.value, 'noise_p': w_noise_p.value,
})
w_status.value = '<span style="color:#00c8ff">โณ Rendering pannelli...</span>'
metrics = compute_overview_metrics(res, w_noise_model.value, w_noise_p.value)
overview_fig = build_panel_overview(res, df_vqe, corr_matrix, w_noise_model.value, w_noise_p.value)
fisica_fig = build_panel_fisica(res, seed=int(w_seed.value))
mosaico_fig = build_panel_mosaico(res)
vqe_fig = build_panel_vqe_results(df_vqe)
md_fig = build_panel_md_results(df_md, corr_matrix)
perf_fig = build_panel_performance(res, run_history)
helix_fig = build_3d_helix_patch(res['n_qubits'], res['prob'])
ham_name = (w_ham_select.value if w_ham_mode.value == 'Libreria Built-in' else 'Custom JSON') if w_ham_enabled.value else None
ham_fig = build_panel_hamiltonian(hamiltonian_values, ham_name or 'nessuna')
mitigation_fig = build_panel_mitigation(mitigation_res, res) if mitigation_res else None
_metrics_html = '<div style="display:flex;flex-wrap:wrap;gap:12px">' + ''.join(
f'<div style="border:1px solid #333;border-radius:6px;padding:6px 10px">'
f'<div style="font-size:11px;color:#888">{m["label"]}</div>'
f'<div style="font-size:15px;font-weight:bold">{m["value"]}</div></div>'
for m in metrics
) + '</div>'
with tab_outputs[0]:
clear_output(wait=True)
display(widgets.HTML(
f'<b>๐ก๏ธ AI Vector-Healing Shield โ Telemetria VQE</b> โ '
f'Fallback: {"Sรฌ" if vqe_ai_meta["fallback_triggered"] else "No"}, '
f'Raggio: {vqe_ai_meta["adaptive_radius_used"]}, '
f'Errore ricostruzione: {vqe_ai_meta["reconstruction_error"]:.4f}'
))
display(widgets.HTML(_metrics_html))
display(overview_fig)
plt.close(overview_fig)
_show(1, fisica_fig)
_show(2, mosaico_fig)
_show(3, vqe_fig)
with tab_outputs[4]:
clear_output(wait=True)
md_badge_color = '#00ff9d' if md_is_real else '#ff9d00'
md_badge_text = 'DATI REALI' if md_is_real else 'MOCK'
display(widgets.HTML(
f'<b style="color:{md_badge_color}">โ {md_badge_text}</b> โ {md_note}'
))
display(widgets.HTML(
f'<b>๐ก๏ธ AI Vector-Healing Shield โ Telemetria MD</b> โ '
f'Fallback: {"Sรฌ" if md_ai_meta["fallback_triggered"] else "No"}, '
f'Raggio: {md_ai_meta["adaptive_radius_used"]}, '
f'Errore ricostruzione: {md_ai_meta["reconstruction_error"]:.4f}'
))
display(md_fig)
plt.close(md_fig)
_show(5, perf_fig)
_show(6, helix_fig) # plotly Figure -- not closed via plt.close (see _show)
_show(7, ham_fig)
_show(8, mitigation_fig)
w_status.value = '<span style="color:#00ff9d">โ Fatto</span>'
w_run.disabled = False
w_run.on_click(_on_run_clicked)
sidebar = widgets.VBox([
widgets.HTML('<b>โ๏ธ Circuito</b>'), w_source_mode, w_circuit, w_qasm_text,
widgets.HTML('<b>๐งฎ Motore</b>'), w_engine,
widgets.HTML('<b>๐ช๏ธ Rumore</b>'), w_noise_model, w_noise_p, w_shots, w_seed, w_double_precision,
widgets.HTML('<b>๐ฉน Error Mitigation (ZNE)</b>'), w_zne_enabled, w_zne_healing, w_zne_target_sigma,
widgets.HTML('<b>๐งช VQE</b>'), w_vqe_enabled, w_vqe_epochs, w_vqe_lr, w_vqe_beta1, w_vqe_beta2, w_confirm_heavy_vqe,
widgets.HTML('<b>๐งฌ Hamiltoniana personalizzata</b>'), w_ham_enabled, w_ham_box,
widgets.HTML('<b>๐ก๏ธ Molecular Dynamics</b>'), w_md_enabled, w_md_steps, w_md_temp,
widgets.HBox([w_run, w_status]),
])
panel = widgets.VBox([sidebar, tabs])
display(panel)
return panel
|