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