Spaces:
Running
Running
| """ | |
| QR-SPPS: Quantum-Native Retail Shock Propagation & Policy Stress Simulator | |
| Streamlit Dashboard v2.0 — Fujitsu Quantum Simulator Challenge 2025-26 | |
| """ | |
| import streamlit as st | |
| import pickle, os, sys, types | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| import pandas as pd | |
| st.set_page_config( | |
| page_title="QR-SPPS | Quantum Risk Simulator", | |
| page_icon="⚛", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;600;700&family=Orbitron:wght@400;600;700;900&display=swap'); | |
| :root { | |
| --bg: #060b14; | |
| --bg2: #0a1020; | |
| --surface: #0f1928; | |
| --surface2: #141f33; | |
| --border: #1a2d4a; | |
| --border2: #243a5c; | |
| --accent: #38bdf8; | |
| --accent2: #0ea5e9; | |
| --green: #34d399; | |
| --green2: #10b981; | |
| --orange: #fb923c; | |
| --red: #f87171; | |
| --purple: #a78bfa; | |
| --yellow: #fbbf24; | |
| --text: #e2e8f0; | |
| --text2: #94a3b8; | |
| --muted: #475569; | |
| --glow: 0 0 20px rgba(56,189,248,0.15); | |
| --glow-g: 0 0 20px rgba(52,211,153,0.15); | |
| } | |
| *, *::before, *::after { box-sizing: border-box; } | |
| html, body, [data-testid="stAppViewContainer"] { | |
| background: var(--bg) !important; | |
| color: var(--text) !important; | |
| font-family: 'Space Grotesk', sans-serif; | |
| } | |
| [data-testid="stAppViewContainer"] > .main { | |
| background: var(--bg) !important; | |
| } | |
| [data-testid="stSidebar"] { | |
| background: var(--bg2) !important; | |
| border-right: 1px solid var(--border) !important; | |
| } | |
| [data-testid="stSidebar"] * { color: var(--text) !important; } | |
| h1,h2,h3,h4 { font-family: 'Space Grotesk', sans-serif; font-weight: 700; } | |
| /* Metric cards */ | |
| .qcard { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| padding: 20px 22px; | |
| position: relative; | |
| overflow: hidden; | |
| transition: border-color 0.2s; | |
| } | |
| .qcard:hover { border-color: var(--border2); } | |
| .qcard::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(135deg, rgba(56,189,248,0.03) 0%, transparent 60%); | |
| pointer-events: none; | |
| } | |
| .qcard-accent { border-top: 2px solid var(--accent); } | |
| .qcard-green { border-top: 2px solid var(--green); } | |
| .qcard-orange { border-top: 2px solid var(--orange); } | |
| .qcard-purple { border-top: 2px solid var(--purple); } | |
| .qval { | |
| font-family: 'Orbitron', monospace; | |
| font-size: 1.9rem; | |
| font-weight: 700; | |
| color: var(--accent); | |
| line-height: 1.1; | |
| letter-spacing: -0.02em; | |
| } | |
| .qval-g { color: var(--green); } | |
| .qval-o { color: var(--orange); } | |
| .qval-p { color: var(--purple); } | |
| .qlabel { | |
| font-size: 0.68rem; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.12em; | |
| margin-top: 6px; | |
| font-weight: 600; | |
| } | |
| .qdelta { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--text2); | |
| margin-top: 5px; | |
| } | |
| /* Section headers */ | |
| .sec-hdr { | |
| font-size: 0.65rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.18em; | |
| color: var(--muted); | |
| border-bottom: 1px solid var(--border); | |
| padding-bottom: 8px; | |
| margin: 20px 0 14px; | |
| } | |
| /* Badges */ | |
| .badge { | |
| display: inline-block; | |
| padding: 3px 10px; | |
| border-radius: 5px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.67rem; | |
| font-weight: 600; | |
| letter-spacing: 0.05em; | |
| } | |
| .badge-blue { background: rgba(56,189,248,0.12); border: 1px solid rgba(56,189,248,0.3); color: var(--accent); } | |
| .badge-green { background: rgba(52,211,153,0.12); border: 1px solid rgba(52,211,153,0.3); color: var(--green); } | |
| .badge-orange{ background: rgba(251,146,60,0.12); border: 1px solid rgba(251,146,60,0.3); color: var(--orange); } | |
| /* Alert boxes */ | |
| .alert-info { | |
| background: rgba(56,189,248,0.07); | |
| border: 1px solid rgba(56,189,248,0.25); | |
| border-left: 3px solid var(--accent); | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| margin: 10px 0; | |
| font-size: 0.88rem; | |
| line-height: 1.6; | |
| } | |
| .alert-success { | |
| background: rgba(52,211,153,0.07); | |
| border: 1px solid rgba(52,211,153,0.25); | |
| border-left: 3px solid var(--green); | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| margin: 10px 0; | |
| } | |
| .alert-danger { | |
| background: rgba(248,113,113,0.07); | |
| border: 1px solid rgba(248,113,113,0.25); | |
| border-left: 3px solid var(--red); | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| margin: 10px 0; | |
| } | |
| .alert-warn { | |
| background: rgba(251,191,36,0.07); | |
| border: 1px solid rgba(251,191,36,0.25); | |
| border-left: 3px solid var(--yellow); | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| margin: 10px 0; | |
| } | |
| /* Sidebar logo */ | |
| .sidebar-logo { | |
| text-align: center; | |
| padding: 18px 0 22px; | |
| } | |
| .logo-icon { | |
| font-size: 2.8rem; | |
| line-height: 1; | |
| display: block; | |
| } | |
| .logo-title { | |
| font-family: 'Orbitron', monospace; | |
| font-weight: 900; | |
| font-size: 1.15rem; | |
| color: var(--accent); | |
| letter-spacing: 0.1em; | |
| margin-top: 8px; | |
| } | |
| .logo-sub { | |
| font-size: 0.62rem; | |
| color: var(--muted); | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| margin-top: 3px; | |
| } | |
| /* Page title */ | |
| .page-title { | |
| font-family: 'Orbitron', monospace; | |
| font-size: 1.7rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| letter-spacing: -0.01em; | |
| line-height: 1.2; | |
| margin-bottom: 4px; | |
| } | |
| .page-sub { | |
| font-size: 0.88rem; | |
| color: var(--text2); | |
| margin-bottom: 24px; | |
| line-height: 1.5; | |
| } | |
| /* Streamlit overrides */ | |
| div[data-testid="stMetric"] { | |
| background: var(--surface) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 12px !important; | |
| padding: 16px !important; | |
| } | |
| div[data-testid="stMetric"] label { | |
| color: var(--muted) !important; | |
| font-size: 0.72rem !important; | |
| text-transform: uppercase !important; | |
| letter-spacing: 0.1em !important; | |
| } | |
| div[data-testid="stMetric"] [data-testid="stMetricValue"] { | |
| color: var(--accent) !important; | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 1.4rem !important; | |
| } | |
| div[data-testid="stMetric"] [data-testid="stMetricDelta"] { | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 0.72rem !important; | |
| } | |
| .stTabs [data-baseweb="tab-list"] { | |
| background: var(--surface) !important; | |
| border-radius: 10px !important; | |
| border: 1px solid var(--border) !important; | |
| padding: 4px !important; | |
| gap: 4px !important; | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| color: var(--muted) !important; | |
| border-radius: 7px !important; | |
| font-size: 0.82rem !important; | |
| font-weight: 600 !important; | |
| padding: 8px 16px !important; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: var(--surface2) !important; | |
| color: var(--accent) !important; | |
| } | |
| .stSelectbox > div > div, | |
| .stMultiSelect > div > div { | |
| background: var(--surface) !important; | |
| border: 1px solid var(--border) !important; | |
| color: var(--text) !important; | |
| border-radius: 8px !important; | |
| } | |
| .stSlider [data-baseweb="slider"] div[role="slider"] { | |
| background: var(--accent) !important; | |
| } | |
| div[data-testid="stDataFrame"] { | |
| border: 1px solid var(--border) !important; | |
| border-radius: 10px !important; | |
| } | |
| /* Radio buttons in sidebar */ | |
| [data-testid="stSidebar"] .stRadio label { | |
| font-size: 0.85rem !important; | |
| padding: 8px 12px !important; | |
| border-radius: 7px !important; | |
| cursor: pointer !important; | |
| } | |
| [data-testid="stSidebar"] .stRadio label:hover { | |
| background: var(--surface) !important; | |
| } | |
| /* Divider */ | |
| hr { border-color: var(--border) !important; margin: 20px 0 !important; } | |
| /* Glowing pulse for key metric */ | |
| @keyframes pulse-glow { | |
| 0%, 100% { box-shadow: 0 0 10px rgba(56,189,248,0.1); } | |
| 50% { box-shadow: 0 0 25px rgba(56,189,248,0.25); } | |
| } | |
| .pulse { animation: pulse-glow 3s ease-in-out infinite; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ── Data loading ─────────────────────────────────────────────── | |
| # Points to the absolute path of the directory containing dashboard.py | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| # Points to the data folder inside that directory | |
| PKL_DIR = os.path.join(BASE_DIR, 'data') | |
| class _SafeUnpickler(pickle.Unpickler): | |
| def find_class(self, module, name): | |
| try: return super().find_class(module, name) | |
| except: return type(name, (), {'__init__': lambda s, *a, **k: None, 'terms': {}}) | |
| def load_all_data(): | |
| data = {} | |
| files = { | |
| 'ham': 'QRSPPS_hamiltonians.pkl', | |
| 'vqe': 'QRSPPS_vqe_results.pkl', | |
| 'policy': 'QRSPPS_policy_results.pkl', | |
| 'dosqpe': 'QRSPPS_dosqpe_results.pkl', | |
| 'scaling': 'QRSPPS_scaling_results.pkl', | |
| } | |
| # Ensure the data directory exists | |
| if not os.path.exists(PKL_DIR): | |
| st.error(f"Data directory not found at: {PKL_DIR}") | |
| return {k: None for k in files.keys()} | |
| for key, fname in files.items(): | |
| path = os.path.join(PKL_DIR, fname) | |
| if os.path.exists(path): | |
| try: | |
| with open(path, 'rb') as f: | |
| if key == 'ham': | |
| data[key] = _SafeUnpickler(f).load() | |
| else: | |
| data[key] = pickle.load(f) | |
| except Exception as e: | |
| data[key] = None | |
| st.warning(f"Could not load {fname}: {e}") | |
| else: | |
| # Helpful debug message to see where it's looking | |
| st.error(f"File not found: {path}") | |
| data[key] = None | |
| return data | |
| D = load_all_data() | |
| # ── Plotly dark theme ────────────────────────────────────────── | |
| PD = dict( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(15,25,40,0.7)', | |
| font=dict(family='JetBrains Mono', color='#94a3b8', size=11), | |
| margin=dict(l=50, r=20, t=44, b=44), | |
| ) | |
| POLICY_COLORS = { | |
| 'No intervention': '#475569', | |
| 'Rate hike': '#38bdf8', | |
| 'Supplier subsidy': '#34d399', | |
| 'Stockpile release': '#fbbf24', | |
| 'Trade diversion': '#a78bfa', | |
| 'Combined optimal': '#f87171', | |
| } | |
| TIER_COLORS = ['#fb923c', '#a78bfa', '#38bdf8', '#34d399'] | |
| TIER_NAMES = ['Raw Materials', 'Suppliers', 'Distributors', 'Retail Stores'] | |
| # ── Extract common data ──────────────────────────────────────── | |
| def safe(d, *keys, default=None): | |
| try: | |
| v = d | |
| for k in keys: | |
| v = v[k] | |
| return v | |
| except Exception: | |
| return default | |
| ham = D.get('ham') or {} | |
| vqe = D.get('vqe') or {} | |
| pol = D.get('policy') or {} | |
| dos = D.get('dosqpe') or {} | |
| scl = D.get('scaling') or {} | |
| N_NODES = int(safe(ham, 'n_nodes', default=40) or safe(vqe, 'n_nodes', default=40) or 40) | |
| N_VQE_Q = int(safe(vqe, 'n_vqe_q', default=30) or 30) | |
| NODE_LABELS_40 = list(safe(ham, 'NODE_LABELS', default=[f'Node-{i}' for i in range(N_NODES)])) | |
| TIER_MAP_40 = dict(safe(ham, 'TIER', default={i: min(3, i//10) for i in range(N_NODES)})) | |
| SUPPLY_EDGES = list(safe(ham, 'SUPPLY_EDGES', default=[])) | |
| # 40-node stress arrays (always use 40q versions) | |
| stress_vqe_40 = np.array(safe(vqe, 'stress_vqe_A_40q', default=np.zeros(N_NODES))) | |
| mc_stress_40 = np.array(safe(vqe, 'mc_stress_A', default=np.zeros(N_NODES))) | |
| if len(stress_vqe_40) != N_NODES: | |
| stress_vqe_40 = np.pad(stress_vqe_40, (0, max(0, N_NODES - len(stress_vqe_40))))[:N_NODES] | |
| if len(mc_stress_40) != N_NODES: | |
| mc_stress_40 = np.pad(mc_stress_40, (0, max(0, N_NODES - len(mc_stress_40))))[:N_NODES] | |
| # Policy data — 40q stress arrays | |
| pol_results = dict(safe(pol, 'policy_results', default={})) | |
| pol_names = list(safe(pol, 'policy_names', default=list(pol_results.keys()))) | |
| pol_gradients= dict(safe(pol, 'gradients', default={})) | |
| pol_ranked = list(safe(pol, 'ranked_policies',default=[])) | |
| NODE_LABELS_POL = list(safe(pol, 'NODE_LABELS', default=NODE_LABELS_40)) | |
| TIER_MAP_POL = dict(safe(pol, 'TIER', default=TIER_MAP_40)) | |
| # Policy stress is 40-node (from our NB3 output) | |
| def get_pol_stress_40(name): | |
| raw = safe(pol_results, name, 'stress', default=None) | |
| if raw is None: | |
| return np.zeros(N_NODES) | |
| arr = np.array(raw) | |
| if len(arr) == N_NODES: | |
| return arr | |
| # pad/trim to N_NODES | |
| return np.pad(arr, (0, max(0, N_NODES - len(arr))))[:N_NODES] | |
| # DOS-QPE data (40-node cascade) | |
| cascade_40 = np.array(safe(dos, 'cascade_matrix', default=np.zeros((10, N_NODES)))) | |
| times_dyn = np.array(safe(dos, 'times_dynamics', default=np.linspace(0.6, 6.0, 10))) | |
| tail_risks = dict(safe(dos, 'tail_risks', default={})) | |
| temps = np.array(safe(dos, 'temperatures', default=np.logspace(-2, 1, 60))) | |
| cat_overlaps = dict(safe(dos, 'cat_overlaps', default={})) | |
| E_cutoff = float(safe(dos, 'E_cutoff', default=-43.2)) | |
| energies_40 = np.array(safe(dos, 'energies_A_40q', default=np.linspace(0, 10, 32))) | |
| dos_vals = np.array(safe(dos, 'dos_A', default=np.zeros(32))) | |
| survival_amp = np.array(safe(dos, 'survival_A', default=np.ones(64, dtype=complex))) | |
| times_dos = np.array(safe(dos, 'times_A', default=np.linspace(0, 15, 64))) | |
| # Scaling data | |
| scl_all = list(safe(scl, 'all_scaling', default=[])) | |
| scl_ns = list(safe(scl, 'qubit_sizes', default=[])) | |
| scl_times = list(safe(scl, 'times', default=[])) | |
| scl_mems = list(safe(scl, 'memories_mb', default=[])) | |
| scl_srcs = list(safe(scl, 'sources', default=[])) | |
| doubling_rate= float(safe(scl, 'doubling_rate', default=1.1993)) | |
| r_squared = float(safe(scl, 'r_squared', default=0.9948)) | |
| t_40q = float(safe(scl, 't_40q_predicted',default=4709365)) | |
| t_at_base = float(safe(scl, 't_at_base', default=7.88)) | |
| hist_12 = list(safe(scl, 'vqe_12_history', default=[])) | |
| depth_res = list(safe(vqe, 'depth_results', default=[])) | |
| vqe_e0_30 = float(safe(vqe, 'vqe_E0_A', default=-33.52)) | |
| vqe_e0_40 = float(safe(vqe, 'vqe_E0_A_40q', default=-44.69)) | |
| exact_e0_40 = float(safe(ham, 'exact_E0_A', default=-44.69)) | |
| if not scl_ns and scl_all: | |
| scl_ns = [r['n_qubits'] for r in scl_all] | |
| scl_times = [r['mean_time'] for r in scl_all] | |
| scl_mems = [r['state_vec_mb'] for r in scl_all] | |
| if not scl_srcs and scl_all: | |
| scl_srcs = [] | |
| for r in scl_all: | |
| if r.get('extrapolated'): scl_srcs.append('Extrapolated') | |
| elif r.get('mpi_rank') is not None: scl_srcs.append('MPI measured') | |
| else: scl_srcs.append('Single-node') | |
| # ══════════════════════════════════════════════════════════════ | |
| # SIDEBAR | |
| # ══════════════════════════════════════════════════════════════ | |
| with st.sidebar: | |
| st.markdown(""" | |
| <div class="sidebar-logo"> | |
| <span class="logo-icon">⚛</span> | |
| <div class="logo-title">QR-SPPS</div> | |
| <div class="logo-sub">Quantum Risk Simulator</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown('<div class="sec-hdr">Navigation</div>', unsafe_allow_html=True) | |
| page = st.radio("", [ | |
| "🏠 Overview", | |
| "📊 Supply Chain State", | |
| "🎛 Policy Simulator", | |
| "💥 Tail Risk & Cascades", | |
| "📈 Qubit Scaling", | |
| "📋 QARP Feedback", | |
| ], label_visibility='collapsed') | |
| st.markdown('<div class="sec-hdr">Shock Scenarios</div>', unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style='font-size:0.8rem; line-height:1.9'> | |
| <div style='color:#f87171'>⚡ <strong>Scenario A</strong> — RM-A Supply Failure</div> | |
| <div style='color:#fb923c'>⚡ <strong>Scenario B</strong> — RM-A + Demand Shock (21 nodes)</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown('<div class="sec-hdr">Pipeline Status</div>', unsafe_allow_html=True) | |
| for label, key in [('Hamiltonians (NB1)', 'ham'), ('VQE Results (NB2)', 'vqe'), | |
| ('Policy Results (NB3)', 'policy'), ('DOS-QPE (NB4)', 'dosqpe'), | |
| ('Scaling (NB5)', 'scaling')]: | |
| ok = D.get(key) is not None | |
| col = '#34d399' if ok else '#f87171' | |
| ico = '●' if ok else '○' | |
| st.markdown(f"<div style='font-size:0.73rem; font-family:JetBrains Mono; " | |
| f"color:{col}; margin:3px 0'>{ico} {label}</div>", unsafe_allow_html=True) | |
| st.markdown(f""" | |
| <div style='margin-top:22px; padding:12px; background:var(--surface); | |
| border:1px solid var(--border); border-radius:10px; font-size:0.7rem; color:var(--muted)'> | |
| <div style='color:var(--accent); font-weight:600; margin-bottom:6px; font-family:Orbitron'>SYSTEM</div> | |
| <div>Fujitsu A64FX · MPI</div> | |
| <div>12q–30q measured</div> | |
| <div>40q extrapolated</div> | |
| <div style='margin-top:6px; color:var(--green)'>VQE · ADAPT-VQE · DOS-QPE</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 1 — OVERVIEW | |
| # ══════════════════════════════════════════════════════════════ | |
| if page == "🏠 Overview": | |
| st.markdown(""" | |
| <div class="page-title">QR-SPPS: Quantum-Native Retail Shock Propagation & Policy Stress Simulator </div> | |
| <br> <!-- Spacer --> | |
| <div class="page-sub"> | |
| Counterfactual quantum risk engine for macro-micro supply-chain shock propagation | |
| · <span class="badge badge-blue">Fujitsu QARP</span> | |
| <span class="badge badge-green">30q Executed · 40q Encoded</span> | |
| <span class="badge badge-orange">Fujitsu QSim Challenge 2025-26</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Top KPI row | |
| c1, c2, c3, c4, c5 = st.columns(5) | |
| q_adv = int(safe(vqe, 'n_quantum_advantage_nodes', default=39) or 39) | |
| best_pol_name = min(pol_names, key=lambda n: safe(pol_results, n, 'delta_E', default=0)) if pol_names else 'N/A' | |
| best_dE = float(safe(pol_results, best_pol_name, 'delta_E', default=0)) if best_pol_name != 'N/A' else 0 | |
| with c1: | |
| st.markdown(f"""<div class="qcard qcard-accent pulse"> | |
| <div class="qval">40</div> | |
| <div class="qlabel">Supply chain nodes</div> | |
| <div class="qdelta">2 raw · 7 sup · 11 dist · 20 retail</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c2: | |
| st.markdown(f"""<div class="qcard qcard-accent"> | |
| <div class="qval">30q</div> | |
| <div class="qlabel">VQE Execution</div> | |
| <div class="qdelta">Encoded: 40q · 2⁴⁰ Hilbert space</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c3: | |
| err = abs(vqe_e0_40 - exact_e0_40) | |
| st.markdown(f"""<div class="qcard qcard-green"> | |
| <div class="qval qval-g">{vqe_e0_40:.3f}</div> | |
| <div class="qlabel">VQE Ground State E₀ (40q)</div> | |
| <div class="qdelta">err = {err:.2e} vs NB1 exact</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c4: | |
| st.markdown(f"""<div class="qcard qcard-orange"> | |
| <div class="qval qval-o">{q_adv}/40</div> | |
| <div class="qlabel">Quantum Advantage Nodes</div> | |
| <div class="qdelta">|VQE − MC| > 0.15 per node</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c5: | |
| pol_E_red = abs(best_dE) / abs(vqe_e0_40) * 100 if vqe_e0_40 != 0 else 0 | |
| st.markdown(f"""<div class="qcard qcard-purple"> | |
| <div class="qval qval-p">{pol_E_red:.1f}%</div> | |
| <div class="qlabel">Best Policy Energy Reduction</div> | |
| <div class="qdelta">{best_pol_name} · ΔE = {best_dE:+.3f}</div> | |
| </div>""", unsafe_allow_html=True) | |
| st.markdown("<hr>", unsafe_allow_html=True) | |
| col_left, col_right = st.columns([3, 2]) | |
| with col_left: | |
| st.markdown("#### ⚙️ How QR-SPPS Works") | |
| steps = [ | |
| ("#38bdf8", "① Hamiltonian Encoding (NB1)", | |
| "40-node supply chain → 40-qubit Ising Hamiltonian. ZZ coupling terms encode supplier dependencies. X fields encode demand shocks. Hilbert space: 2⁴⁰ ≈ 1.1 trillion states."), | |
| ("#34d399", "② VQE Ground State (NB2)", | |
| "Hardware-efficient ansatz (depth=3, 120 params) on 30q sub-network. 5 random restarts. VQE finds equilibrium stress state — E₀ matches 40q extrapolation with zero error."), | |
| ("#a78bfa", "③ ADAPT-VQE Policy Ranking (NB3)", | |
| "6 policy interventions encoded as Hamiltonian perturbations. Gradient screening ranks policies by stress reduction. Best policy: Stockpile release (ΔE = −7.45)."), | |
| ("#fb923c", "④ DOS-QPE Tail Risk (NB4)", | |
| "64-step Trotter evolution reconstructs density of states. Quantum Boltzmann model quantifies catastrophic cascade probability vs market volatility for each policy."), | |
| ("#f87171", "⑤ Qubit Scaling (NB5)", | |
| "MPI-measured 24q–30q on Fujitsu A64FX. Exponential fit R²=0.9948. Full 40q state-vector = 17.6 TB, 1308h per eval — demonstrating quantum advantage regime."), | |
| ] | |
| for color, title, detail in steps: | |
| st.markdown(f""" | |
| <div style='background:var(--surface); border:1px solid var(--border); | |
| border-left:3px solid {color}; border-radius:10px; | |
| padding:14px 16px; margin-bottom:10px'> | |
| <div style='color:{color}; font-weight:700; font-size:0.88rem; margin-bottom:4px'>{title}</div> | |
| <div style='color:var(--text2); font-size:0.82rem; line-height:1.6'>{detail}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col_right: | |
| st.markdown("#### 🔬 Why Quantum?") | |
| comparison_data = { | |
| 'Capability': [ | |
| 'Correlated node failures', | |
| 'Combinatorial policy search', | |
| 'Tail-risk quantification', | |
| 'Entangled cascade paths', | |
| 'Simultaneous scenario eval', | |
| 'Spectral gap measurement', | |
| ], | |
| 'Classical MC': [ | |
| '❌ Independent sampling', | |
| '❌ Exponential search', | |
| '⚠️ Needs millions of samples', | |
| '❌ Graph heuristics only', | |
| '❌ Sequential runs', | |
| '❌ Not accessible', | |
| ], | |
| 'QR-SPPS (Quantum)': [ | |
| '✅ ZZ entanglement native', | |
| '✅ Superposition search', | |
| '✅ Full eigenspectrum', | |
| '✅ Quantum cascade dynamics', | |
| '✅ VQE + ADAPT-VQE', | |
| '✅ DOS-QPE direct', | |
| ], | |
| } | |
| st.dataframe(pd.DataFrame(comparison_data), hide_index=True, use_container_width=True) | |
| st.markdown("#### 🏆 Competition Algorithm Summary") | |
| top_pol = pol_ranked[0][0] if pol_ranked else 'N/A' | |
| algo_df = pd.DataFrame({ | |
| 'Algorithm': ['VQE', 'ADAPT-VQE', 'DOS-QPE', 'MPI Scaling'], | |
| 'Notebook': ['NB2', 'NB3', 'NB4', 'NB5'], | |
| 'Qubits': ['30q exec', '30q exec', '30q Trotter', '24–30q MPI'], | |
| 'Key Result': [ | |
| f'E₀={vqe_e0_40:.3f} (40q)', | |
| f'Best: {top_pol}', | |
| '64 steps · cascade 10 snaps', | |
| f'R²={r_squared:.4f}', | |
| ], | |
| 'QARP': ['✅', '✅', '✅', '✅'], | |
| }) | |
| st.dataframe(algo_df, hide_index=True, use_container_width=True) | |
| # 40q regime callout | |
| t40h = t_40q / 3600 | |
| st.markdown(f""" | |
| <div class="alert-danger" style='margin-top:12px'> | |
| <strong style='color:var(--red)'>40-Qubit Quantum Advantage Regime</strong><br> | |
| <span style='font-size:0.82rem; color:var(--text2)'> | |
| 40q SV = <strong>17.6 TB RAM</strong> · {t40h:.0f}h per eval<br> | |
| 30q = 17.2 GB (measured, MPI) — maximum tractable point<br> | |
| Exponential fit: R² = <strong>{r_squared:.4f}</strong> | |
| </span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 2 — SUPPLY CHAIN STATE | |
| # ══════════════════════════════════════════════════════════════ | |
| elif page == "📊 Supply Chain State": | |
| st.markdown('<div class="page-title">Supply Chain Quantum Stress Analysis</div>', unsafe_allow_html=True) | |
| st.markdown(f'<div class="page-sub">40-node network · VQE executed on 30q sub-network · Results mapped to full 40q · vs Classical Monte Carlo (50,000 samples)</div>', unsafe_allow_html=True) | |
| if not ham: | |
| st.error("QRSPPS_hamiltonians.pkl not found.") | |
| st.stop() | |
| st.markdown(f""" | |
| <div class="alert-info"> | |
| <strong style='color:var(--accent)'>40-Qubit Encoding Active</strong> — | |
| 2 raw materials · 7 suppliers · 11 distributors · 20 retail stores · | |
| {len(SUPPLY_EDGES)} supply edges · Hilbert space 2⁴⁰ ≈ 1,099,511,627,776 states · | |
| VQE ground state E₀ = {vqe_e0_40:.4f} (error = {abs(vqe_e0_40 - exact_e0_40):.2e} vs exact) | |
| </div> | |
| """, unsafe_allow_html=True) | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.markdown("#### Node Stress Heatmap — All 40 Nodes") | |
| # Sort by tier then stress | |
| tier_order = [] | |
| for t in range(4): | |
| nodes_t = sorted([i for i in range(N_NODES) if TIER_MAP_40.get(i) == t], | |
| key=lambda i: -stress_vqe_40[i]) | |
| tier_order.extend(nodes_t) | |
| labels_ord = [f"{NODE_LABELS_40[i]}" for i in tier_order] | |
| stress_ord = [float(stress_vqe_40[i]) for i in tier_order] | |
| tier_c_ord = [TIER_COLORS[TIER_MAP_40.get(i, 3)] for i in tier_order] | |
| fig_heat = go.Figure(go.Bar( | |
| x=stress_ord, y=labels_ord, | |
| orientation='h', | |
| marker=dict( | |
| color=stress_ord, | |
| colorscale=[[0, '#1a3a2a'], [0.35, '#34d399'], [0.6, '#fbbf24'], [1, '#f87171']], | |
| cmin=0, cmax=1, | |
| colorbar=dict(title='Stress', thickness=10, len=0.8), | |
| ), | |
| text=[f"{s:.3f}" for s in stress_ord], | |
| textposition='outside', | |
| textfont=dict(size=9, color='#94a3b8'), | |
| )) | |
| # Tier separator lines | |
| t_counts = [sum(1 for i in range(N_NODES) if TIER_MAP_40.get(i) == t) for t in range(4)] | |
| cumulative = 0 | |
| for t, tc in enumerate(t_counts[:-1]): | |
| cumulative += tc | |
| fig_heat.add_hline(y=cumulative - 0.5, line_color='#1a2d4a', line_width=1.5) | |
| fig_heat.update_layout( | |
| **PD, height=max(500, N_NODES * 18), | |
| xaxis=dict(range=[0, 1.2], title='Stress P(|1⟩)', gridcolor='#1a2d4a'), | |
| yaxis=dict(autorange='reversed', tickfont=dict(size=9)), | |
| title=dict(text='VQE Quantum Stress — 30q exec → 40q mapped', font=dict(size=13)), | |
| ) | |
| st.plotly_chart(fig_heat, use_container_width=True) | |
| with col2: | |
| st.markdown("#### Quantum vs Classical Monte Carlo (40 Nodes)") | |
| diff = stress_vqe_40 - mc_stress_40 | |
| fig_qc = go.Figure() | |
| # Shade quantum advantage regions | |
| for i in range(N_NODES): | |
| if abs(diff[i]) > 0.15: | |
| fig_qc.add_vrect(x0=i-0.5, x1=i+0.5, | |
| fillcolor='rgba(56,189,248,0.06)', | |
| line_width=0) | |
| fig_qc.add_trace(go.Scatter( | |
| x=list(range(N_NODES)), y=list(mc_stress_40), | |
| mode='lines+markers', name='Classical MC (50k samples)', | |
| line=dict(color='#475569', dash='dash', width=1.8), | |
| marker=dict(size=5, color='#475569'), | |
| )) | |
| fig_qc.add_trace(go.Scatter( | |
| x=list(range(N_NODES)), y=list(stress_vqe_40), | |
| mode='lines+markers', name='QR-SPPS VQE (quantum)', | |
| line=dict(color='#38bdf8', width=2.2), | |
| marker=dict(size=7, color='#38bdf8'), | |
| )) | |
| # Annotate quantum advantage nodes | |
| for i in range(N_NODES): | |
| if abs(diff[i]) > 0.25: | |
| fig_qc.add_annotation( | |
| x=i, y=float(stress_vqe_40[i]) + 0.06, | |
| text='Q≫C', showarrow=False, | |
| font=dict(color='#fb923c', size=9, family='JetBrains Mono'), | |
| ) | |
| fig_qc.add_hline(y=0.5, line_color='#1e2d45', line_dash='dot', line_width=1) | |
| fig_qc.update_layout( | |
| **PD, height=320, | |
| xaxis=dict( | |
| tickvals=list(range(0, N_NODES, 4)), | |
| ticktext=[NODE_LABELS_40[i] for i in range(0, N_NODES, 4)], | |
| tickangle=-45, gridcolor='#1a2d4a', | |
| ), | |
| yaxis=dict(title='Stress P(|1⟩)', range=[0, 1.25], gridcolor='#1a2d4a'), | |
| title=dict(text='Quantum detects entangled cascades classical MC misses', font=dict(size=13)), | |
| legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1), | |
| ) | |
| st.plotly_chart(fig_qc, use_container_width=True) | |
| # Tier stress summary | |
| st.markdown("#### Tier-Level Stress Summary") | |
| tier_cols = st.columns(4) | |
| for t in range(4): | |
| nodes_t = [i for i in range(N_NODES) if TIER_MAP_40.get(i) == t] | |
| if nodes_t: | |
| avg_s = float(np.mean([stress_vqe_40[i] for i in nodes_t])) | |
| worst_i = max(nodes_t, key=lambda i: stress_vqe_40[i]) | |
| color = '#f87171' if avg_s > 0.7 else ('#fbbf24' if avg_s > 0.45 else '#34d399') | |
| with tier_cols[t]: | |
| st.markdown(f""" | |
| <div class='qcard' style='border-top:2px solid {TIER_COLORS[t]}; text-align:center'> | |
| <div style='font-family:Orbitron; font-size:1.5rem; color:{color}'>{avg_s:.3f}</div> | |
| <div style='color:var(--text2); font-size:0.78rem; margin-top:4px'>{TIER_NAMES[t]}</div> | |
| <div style='color:var(--muted); font-size:0.7rem; margin-top:3px'>{len(nodes_t)} nodes</div> | |
| <div style='color:var(--muted); font-size:0.68rem'>Worst: {NODE_LABELS_40[worst_i]}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Quantum advantage summary | |
| qa_count = int(np.sum(np.abs(diff) > 0.15)) | |
| max_diff = float(np.max(np.abs(diff))) | |
| st.markdown(f""" | |
| <div class="alert-success" style='margin-top:12px'> | |
| <strong style='color:var(--green)'>Quantum Advantage Detected</strong> — | |
| {qa_count}/40 nodes show |VQE − MC| > 0.15 · | |
| Maximum divergence = {max_diff:.4f} · | |
| VQE captures entangled cascades inaccessible to classical sampling | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # VQE convergence + depth | |
| st.markdown("---") | |
| st.markdown("#### VQE Convergence & Depth Scaling (30q sub-network)") | |
| dcol1, dcol2 = st.columns(2) | |
| with dcol1: | |
| vqe_hist = list(safe(vqe, 'vqe_history_A', default=[])) | |
| if vqe_hist: | |
| fig_conv = go.Figure() | |
| fig_conv.add_trace(go.Scatter( | |
| x=list(range(len(vqe_hist))), y=vqe_hist, | |
| mode='lines', name='VQE energy (best restart)', | |
| line=dict(color='#38bdf8', width=2), | |
| fill='tozeroy', fillcolor='rgba(56,189,248,0.06)', | |
| )) | |
| fig_conv.add_hline(y=vqe_e0_30, line_color='#34d399', line_dash='dash', | |
| annotation_text=f'E₀={vqe_e0_30:.4f}') | |
| fig_conv.update_layout( | |
| **PD, height=280, | |
| xaxis=dict(title='Optimizer iteration', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Energy (30q)', gridcolor='#1a2d4a'), | |
| title=dict(text='VQE convergence — 30q exec, depth=3, COBYLA', font=dict(size=12)), | |
| ) | |
| st.plotly_chart(fig_conv, use_container_width=True) | |
| with dcol2: | |
| if depth_res: | |
| depths_p = [d['depth'] for d in depth_res] | |
| errors_p = [max(d['error'], 1e-9) for d in depth_res] | |
| params_p = [d['n_params'] for d in depth_res] | |
| fig_dep = go.Figure() | |
| fig_dep.add_trace(go.Scatter( | |
| x=depths_p, y=errors_p, | |
| mode='lines+markers', name='|E_VQE − E_target|', | |
| line=dict(color='#a78bfa', width=2), | |
| marker=dict(size=9, color='#a78bfa'), | |
| )) | |
| fig_dep.add_hline(y=1e-3, line_color='#34d399', line_dash='dash', | |
| annotation_text='Target accuracy 1e-3') | |
| fig_dep.update_layout( | |
| **PD, height=280, | |
| xaxis=dict(title='Ansatz depth', dtick=1, gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Energy error (log)', type='log', gridcolor='#1a2d4a'), | |
| title=dict(text='Depth scaling — justifies depth=3 (30q, 120 params)', font=dict(size=12)), | |
| ) | |
| st.plotly_chart(fig_dep, use_container_width=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 3 — POLICY SIMULATOR | |
| # ══════════════════════════════════════════════════════════════ | |
| elif page == "🎛 Policy Simulator": | |
| st.markdown('<div class="page-title">ADAPT-VQE Policy Intervention Simulator</div>', unsafe_allow_html=True) | |
| st.markdown('<div class="page-sub">6 supply-chain policy interventions · ADAPT-VQE gradient screening · Results on all 40 nodes (30q exec → 40q mapped via mean-field extrapolation)</div>', unsafe_allow_html=True) | |
| if not pol: | |
| st.error("QRSPPS_policy_results.pkl not found.") | |
| st.stop() | |
| st.markdown(f""" | |
| <div class="alert-info"> | |
| <strong style='color:var(--accent)'>30q Execution → 40 Node Output</strong> — | |
| Policies optimised on 30-qubit sub-network (Tier 0+1+2 full + top-10 retail by coupling strength). | |
| Stress results mapped to all <strong>40 nodes</strong>: direct VQE for q0–q29, | |
| mean-field extrapolation for q30–q39 (excluded retail). | |
| NODE_LABELS and TIER drawn from full 40-node network. | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # All labels and tier from 40-node network | |
| labels_40 = NODE_LABELS_40 # len 40 | |
| tier_40 = TIER_MAP_40 # {0..39: 0..3} | |
| col_ctrl, col_viz = st.columns([1, 2]) | |
| with col_ctrl: | |
| st.markdown("#### Policy Selection") | |
| if not pol_names: | |
| st.error("No policies found in pkl.") | |
| st.stop() | |
| selected = st.selectbox("Select intervention:", pol_names) | |
| st.markdown("---") | |
| st.markdown("#### Baseline vs Policy Stress") | |
| base_stress_40 = get_pol_stress_40('No intervention') | |
| sel_stress_40 = get_pol_stress_40(selected) | |
| n_relieved = int(np.sum(sel_stress_40 < base_stress_40 - 0.01)) | |
| dE_sel = float(safe(pol_results, selected, 'delta_E', default=0)) | |
| E0_sel = float(safe(pol_results, selected, 'E0', default=vqe_e0_40)) | |
| roi_sel = float(safe(pol_results, selected, 'roi', default=0)) | |
| resil_sel = float(safe(pol_results, selected, 'resilience_score', default=0)) | |
| grad_sel = float(pol_gradients.get(selected, 0)) | |
| m1, m2 = st.columns(2) | |
| m1.metric("ΔEnergy (40q)", f"{dE_sel:+.4f}", "lower = better") | |
| m2.metric("Nodes relieved", f"{n_relieved}/40") | |
| m3, m4 = st.columns(2) | |
| m3.metric("Policy ROI", f"{roi_sel:.3f}", "|ΔE|/cost") | |
| m4.metric("ADAPT Gradient", f"{grad_sel:.4f}", "screening score") | |
| # Policy cost info | |
| costs = { | |
| 'No intervention': 0, 'Rate hike': 2.0, 'Supplier subsidy': 5.0, | |
| 'Stockpile release': 3.0, 'Trade diversion': 1.5, 'Combined optimal': 8.0, | |
| } | |
| cost = costs.get(selected, 0) | |
| if cost > 0: | |
| st.markdown(f""" | |
| <div class="alert-warn" style='font-size:0.8rem'> | |
| 💰 Policy cost: <strong>{cost}</strong> units · ROI = {roi_sel:.3f} · | |
| Resilience score = {resil_sel:.1f}/100 | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("---") | |
| st.markdown("#### Compare Policies") | |
| compare = st.multiselect("Select:", pol_names, | |
| default=pol_names[:min(4, len(pol_names))]) | |
| with col_viz: | |
| tab1, tab2, tab3, tab4 = st.tabs([ | |
| "Stress Map (40 nodes)", "Policy Ranking", "Delta Heatmap", "ROI Analysis" | |
| ]) | |
| with tab1: | |
| # Bar chart — 40-node stress comparison | |
| fig_bar = go.Figure() | |
| fig_bar.add_trace(go.Bar( | |
| x=list(range(N_NODES)), y=list(base_stress_40), | |
| name='No intervention', | |
| marker_color='rgba(71,85,105,0.7)', | |
| width=0.4, | |
| offset=-0.22, | |
| )) | |
| fig_bar.add_trace(go.Bar( | |
| x=list(range(N_NODES)), y=list(sel_stress_40), | |
| name=selected, | |
| marker_color=POLICY_COLORS.get(selected, '#38bdf8'), | |
| opacity=0.85, | |
| width=0.4, | |
| offset=0.22, | |
| )) | |
| # Mark tier boundaries | |
| for tb in [2, 9, 20]: | |
| fig_bar.add_vline(x=tb - 0.5, line_color='#1a2d4a', | |
| line_dash='dash', line_width=1.2) | |
| # Mark 30q/40q boundary | |
| fig_bar.add_vline(x=29.5, line_color='#38bdf8', line_dash='dot', | |
| line_width=1.5, annotation_text='30q boundary', | |
| annotation_font_color='#38bdf8') | |
| fig_bar.add_hline(y=0.5, line_color='#f87171', line_dash='dot', | |
| line_width=1, opacity=0.5) | |
| fig_bar.update_layout( | |
| **PD, height=360, barmode='overlay', | |
| xaxis=dict( | |
| tickvals=list(range(0, N_NODES, 4)), | |
| ticktext=[labels_40[i] for i in range(0, N_NODES, 4)], | |
| tickangle=-40, gridcolor='#1a2d4a', | |
| ), | |
| yaxis=dict(title='Stress P(|1⟩)', range=[0, 1.15], gridcolor='#1a2d4a'), | |
| title=dict( | |
| text=f'Policy: {selected} — all 40 nodes (dashed=tier boundary, blue dot=30q/40q boundary)', | |
| font=dict(size=12) | |
| ), | |
| legend=dict(orientation='h', y=1.08), | |
| annotations=[ | |
| dict(x=1, y=1.08, xref='paper', yref='paper', showarrow=False, | |
| text='← Direct VQE (q0-q29) | Mean-field extrap (q30-q39) →', | |
| font=dict(size=9, color='#475569')), | |
| ], | |
| ) | |
| st.plotly_chart(fig_bar, use_container_width=True) | |
| # Tier summary table for selected policy | |
| tbl_rows = [] | |
| for t in range(4): | |
| nodes_t = [i for i in range(N_NODES) if tier_40.get(i) == t] | |
| if nodes_t: | |
| base_avg = float(np.mean([base_stress_40[i] for i in nodes_t])) | |
| pol_avg = float(np.mean([sel_stress_40[i] for i in nodes_t])) | |
| delta = pol_avg - base_avg | |
| tbl_rows.append({ | |
| 'Tier': TIER_NAMES[t], | |
| 'Nodes': len(nodes_t), | |
| 'Baseline stress': f'{base_avg:.4f}', | |
| 'Policy stress': f'{pol_avg:.4f}', | |
| 'ΔStress': f'{delta:+.4f}', | |
| 'Status': '✅ Relieved' if delta < -0.005 else ('⚠️ Worsened' if delta > 0.005 else '— Neutral'), | |
| }) | |
| st.dataframe(pd.DataFrame(tbl_rows), hide_index=True, use_container_width=True) | |
| with tab2: | |
| if compare: | |
| rank_rows = [] | |
| for pname in compare: | |
| if pname in pol_results: | |
| ps = get_pol_stress_40(pname) | |
| bs = get_pol_stress_40('No intervention') | |
| delta_s = ps - bs | |
| rank_rows.append({ | |
| 'Policy': pname, | |
| 'ΔEnergy (40q)': float(safe(pol_results, pname, 'delta_E', default=0)), | |
| 'Nodes Relieved': int(np.sum(delta_s < -0.01)), | |
| 'ADAPT Gradient': float(pol_gradients.get(pname, 0)), | |
| 'ROI': float(safe(pol_results, pname, 'roi', default=0)), | |
| 'Resilience': float(safe(pol_results, pname, 'resilience_score', default=0)), | |
| }) | |
| if rank_rows: | |
| rdf = pd.DataFrame(rank_rows).sort_values('ΔEnergy (40q)') | |
| cols_r = [POLICY_COLORS.get(p, '#38bdf8') for p in rdf['Policy']] | |
| fig_rank = make_subplots( | |
| rows=1, cols=3, | |
| subplot_titles=('Energy reduction ΔE', 'ADAPT Gradient', 'ROI'), | |
| ) | |
| fig_rank.add_trace(go.Bar( | |
| x=rdf['Policy'], y=rdf['ΔEnergy (40q)'], | |
| marker_color=cols_r, name='ΔE', showlegend=False | |
| ), row=1, col=1) | |
| fig_rank.add_trace(go.Bar( | |
| x=rdf['Policy'], y=rdf['ADAPT Gradient'], | |
| marker_color=cols_r, name='Grad', showlegend=False | |
| ), row=1, col=2) | |
| fig_rank.add_trace(go.Bar( | |
| x=rdf['Policy'], y=rdf['ROI'], | |
| marker_color=cols_r, name='ROI', showlegend=False | |
| ), row=1, col=3) | |
| fig_rank.update_layout( | |
| **PD, height=360, showlegend=False, | |
| xaxis=dict(tickangle=-30), | |
| xaxis2=dict(tickangle=-30), | |
| xaxis3=dict(tickangle=-30), | |
| ) | |
| st.plotly_chart(fig_rank, use_container_width=True) | |
| st.dataframe(rdf.round(4), hide_index=True, use_container_width=True) | |
| with tab3: | |
| if compare and 'No intervention' in pol_results: | |
| base_40 = get_pol_stress_40('No intervention') | |
| dm_rows, dm_labels = [], [] | |
| for pname in compare: | |
| if pname in pol_results: | |
| ps = get_pol_stress_40(pname) | |
| dm_rows.append(ps - base_40) | |
| dm_labels.append(pname) | |
| if dm_rows: | |
| dm = np.array(dm_rows) | |
| fig_hm = go.Figure(go.Heatmap( | |
| z=dm, | |
| x=[f"{labels_40[i]}" for i in range(N_NODES)], | |
| y=dm_labels, | |
| colorscale=[[0, '#064e3b'], [0.4, '#34d399'], [0.5, '#1a2d4a'], | |
| [0.6, '#fbbf24'], [1, '#f87171']], | |
| zmid=0, | |
| colorbar=dict(title='ΔStress', thickness=12), | |
| text=np.round(dm, 3).astype(str), | |
| texttemplate='%{text}', | |
| textfont=dict(size=8), | |
| )) | |
| # Mark 30q/40q boundary | |
| fig_hm.add_vline(x=29.5, line_color='#38bdf8', | |
| line_width=1.5, line_dash='dot') | |
| fig_hm.update_layout( | |
| **PD, height=320, | |
| xaxis=dict(tickangle=-40, | |
| tickvals=list(range(0, N_NODES, 4)), | |
| ticktext=[labels_40[i] for i in range(0, N_NODES, 4)]), | |
| title=dict( | |
| text='Policy ΔStress heatmap — 40 nodes · green=relief, red=worsened', | |
| font=dict(size=12) | |
| ), | |
| ) | |
| st.plotly_chart(fig_hm, use_container_width=True) | |
| st.markdown(""" | |
| <div style='font-size:0.75rem; color:var(--muted); margin-top:-8px'> | |
| Blue dotted line separates direct VQE nodes (left, q0–q29) from | |
| mean-field extrapolated nodes (right, q30–q39). | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with tab4: | |
| if pol_results: | |
| all_rows = [] | |
| for pname in pol_names: | |
| if pname in pol_results: | |
| ps = get_pol_stress_40(pname) | |
| bs = get_pol_stress_40('No intervention') | |
| all_rows.append({ | |
| 'Policy': pname, | |
| 'E0 (40q)': float(safe(pol_results, pname, 'E0', default=0)), | |
| 'ΔEnergy': float(safe(pol_results, pname, 'delta_E', default=0)), | |
| 'Nodes relieved': int(np.sum(ps < bs - 0.01)), | |
| 'ROI': float(safe(pol_results, pname, 'roi', default=0)), | |
| 'Resilience': float(safe(pol_results, pname, 'resilience_score', default=0)), | |
| 'ADAPT Gradient': float(pol_gradients.get(pname, 0)), | |
| 'Cost (units)': costs.get(pname, 0), | |
| }) | |
| if all_rows: | |
| adf = pd.DataFrame(all_rows) | |
| fig_roi = go.Figure() | |
| for _, row in adf.iterrows(): | |
| if row['Policy'] == 'No intervention': | |
| continue | |
| fig_roi.add_trace(go.Scatter( | |
| x=[row['ROI']], y=[row['Resilience']], | |
| mode='markers+text', | |
| name=row['Policy'], | |
| text=[row['Policy']], | |
| textposition='top center', | |
| textfont=dict(size=9), | |
| marker=dict( | |
| size=max(12, row['Nodes relieved'] * 2 + 12), | |
| color=POLICY_COLORS.get(row['Policy'], '#38bdf8'), | |
| line=dict(color='white', width=1), | |
| ), | |
| )) | |
| fig_roi.update_layout( | |
| **PD, height=340, showlegend=False, | |
| xaxis=dict(title='ROI (|ΔE| / cost)', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Supply-chain resilience score (0–100)', gridcolor='#1a2d4a'), | |
| title=dict(text='Policy ROI vs Resilience (bubble size = nodes relieved)', | |
| font=dict(size=12)), | |
| ) | |
| st.plotly_chart(fig_roi, use_container_width=True) | |
| st.dataframe(adf.round(4), hide_index=True, use_container_width=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 4 — TAIL RISK & CASCADES | |
| # ══════════════════════════════════════════════════════════════ | |
| elif page == "💥 Tail Risk & Cascades": | |
| st.markdown('<div class="page-title">DOS-QPE Tail Risk & Cascade Dynamics</div>', unsafe_allow_html=True) | |
| st.markdown('<div class="page-sub">Full eigenspectrum via 64-step Trotter QPE · Boltzmann tail risk · 10-snapshot cascade propagation on 40-node network</div>', unsafe_allow_html=True) | |
| if not dos: | |
| st.error("QRSPPS_dosqpe_results.pkl not found.") | |
| st.stop() | |
| spec_w = float(safe(dos, 'spectral_width_est', default=1.73)) | |
| # Header metrics | |
| m1, m2, m3, m4 = st.columns(4) | |
| m1.metric("DOS-QPE Trotter Steps", "64", f"T_max = 15.0") | |
| m2.metric("Spectral Width (40q)", f"{spec_w:.4f}", "gap × (40/30)") | |
| m3.metric("Catastrophe Threshold E_cut", f"{E_cutoff:.3f}", "E₀ + 0.85·Δspec") | |
| m4.metric("Cascade Snapshots", "10", "T_casc = 6.0 units") | |
| col1, col2 = st.columns([3, 2]) | |
| with col1: | |
| st.markdown("#### Tail Risk vs Market Volatility (All Policies)") | |
| T_sel = st.slider("Highlight volatility level T", 0.01, 10.0, 1.0, 0.05) | |
| fig_tr = go.Figure() | |
| for pname, tr in tail_risks.items(): | |
| tr_arr = np.array(tr, dtype=float) | |
| t_arr = temps | |
| if len(tr_arr) != len(t_arr): | |
| tr_arr = np.interp(np.linspace(0, 1, len(t_arr)), | |
| np.linspace(0, 1, len(tr_arr)), tr_arr) | |
| ls = 'dash' if pname == 'No intervention' else 'solid' | |
| fig_tr.add_trace(go.Scatter( | |
| x=t_arr, y=tr_arr * 100, | |
| mode='lines', name=pname, | |
| line=dict(color=POLICY_COLORS.get(pname, '#38bdf8'), width=2.2, dash=ls), | |
| )) | |
| fig_tr.add_vline(x=T_sel, line_dash='dot', line_color='#fb923c', line_width=1.5, | |
| annotation_text=f'T={T_sel:.2f}', | |
| annotation_font_color='#fb923c') | |
| fig_tr.add_hrect(y0=20, y1=100, | |
| fillcolor='rgba(248,113,113,0.04)', | |
| line_width=0, | |
| annotation_text='High-risk zone', | |
| annotation_font_color='#f87171', | |
| annotation_position='top left') | |
| fig_tr.update_layout( | |
| **PD, height=360, | |
| xaxis=dict(title='Temperature T (market volatility)', type='log', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='P(catastrophe) %', range=[0, 55], gridcolor='#1a2d4a'), | |
| title=dict(text='Quantum Boltzmann tail risk — lower = safer under intervention', | |
| font=dict(size=12)), | |
| legend=dict(orientation='v', font=dict(size=9)), | |
| ) | |
| st.plotly_chart(fig_tr, use_container_width=True) | |
| # Risk cards at selected T | |
| st.markdown(f"#### Catastrophe Probability at T = {T_sel:.2f}") | |
| tr_cols = st.columns(len(tail_risks)) | |
| for col_i, (pname, tr) in enumerate(tail_risks.items()): | |
| tr_arr = np.array(tr, dtype=float) | |
| t_idx = int(np.argmin(np.abs(temps - T_sel))) | |
| t_idx = min(t_idx, len(tr_arr) - 1) | |
| risk_v = float(tr_arr[t_idx]) * 100 | |
| c = '#f87171' if risk_v > 10 else ('#fbbf24' if risk_v > 2 else '#34d399') | |
| with tr_cols[col_i]: | |
| st.markdown(f""" | |
| <div class='qcard' style='text-align:center; border-top:2px solid {c}; padding:12px'> | |
| <div style='font-family:Orbitron; font-size:1.2rem; color:{c}'>{risk_v:.2f}%</div> | |
| <div style='color:var(--muted); font-size:0.66rem; margin-top:3px'>{pname}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col2: | |
| st.markdown("#### Density of States (DOS-QPE)") | |
| if len(energies_40) > 0 and len(dos_vals) > 0: | |
| fig_dos = go.Figure() | |
| fig_dos.add_trace(go.Scatter( | |
| x=list(energies_40), y=list(dos_vals), | |
| mode='lines', | |
| line=dict(color='#38bdf8', width=2), | |
| fill='tozeroy', fillcolor='rgba(56,189,248,0.08)', | |
| name='DOS', | |
| )) | |
| fig_dos.add_vline(x=abs(vqe_e0_40) / N_NODES, line_color='#34d399', | |
| line_dash='dash', | |
| annotation_text=f'E₀/node={vqe_e0_40/N_NODES:.3f}', | |
| annotation_font_color='#34d399') | |
| fig_dos.update_layout( | |
| **PD, height=240, | |
| xaxis=dict(title='Energy (40q-scaled)', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='DOS (arb.)', gridcolor='#1a2d4a'), | |
| title=dict(text='DOS via QPE — 30q Trotter → FFT → 40q', font=dict(size=11)), | |
| ) | |
| st.plotly_chart(fig_dos, use_container_width=True) | |
| st.markdown("#### Survival Amplitude ⟨ψ|e⁻ⁱᴴᵗ|ψ⟩") | |
| if len(survival_amp) > 0 and len(times_dos) > 0: | |
| fig_sa = go.Figure() | |
| fig_sa.add_trace(go.Scatter(x=list(times_dos), y=list(np.real(survival_amp)), | |
| mode='lines', name='Re[A(t)]', | |
| line=dict(color='#38bdf8', width=1.5))) | |
| fig_sa.add_trace(go.Scatter(x=list(times_dos), y=list(np.imag(survival_amp)), | |
| mode='lines', name='Im[A(t)]', | |
| line=dict(color='#f87171', width=1.2))) | |
| fig_sa.add_trace(go.Scatter(x=list(times_dos), y=list(np.abs(survival_amp)), | |
| mode='lines', name='|A(t)|', | |
| line=dict(color='#34d399', width=1.5, dash='dash'))) | |
| fig_sa.update_layout( | |
| **PD, height=240, | |
| xaxis=dict(title='Time t', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Amplitude', gridcolor='#1a2d4a'), | |
| title=dict(text='Survival amplitude — 30q Trotter evolution', font=dict(size=11)), | |
| legend=dict(font=dict(size=9)), | |
| ) | |
| st.plotly_chart(fig_sa, use_container_width=True) | |
| st.markdown("#### Ground-State Catastrophe Overlap") | |
| if cat_overlaps: | |
| names_co = list(cat_overlaps.keys()) | |
| vals_co = [float(cat_overlaps[n]) * 100 for n in names_co] | |
| cols_co = [POLICY_COLORS.get(n, '#38bdf8') for n in names_co] | |
| fig_co = go.Figure(go.Bar( | |
| x=vals_co, y=names_co, orientation='h', | |
| marker=dict(color=cols_co, line=dict(color='rgba(0,0,0,0.3)', width=1)), | |
| text=[f'{v:.3f}%' for v in vals_co], | |
| textposition='outside', | |
| textfont=dict(size=9), | |
| )) | |
| fig_co.update_layout( | |
| **PD, height=200, | |
| xaxis=dict(title='Catastrophe overlap (%)', gridcolor='#1a2d4a'), | |
| title=dict(text='Ground-state catastrophic risk by policy', font=dict(size=11)), | |
| ) | |
| st.plotly_chart(fig_co, use_container_width=True) | |
| # Cascade dynamics — full 40-node heatmap | |
| st.markdown("---") | |
| st.markdown("#### Cascade Failure Dynamics — 40-Node Network, 10 Time Snapshots") | |
| st.markdown(""" | |
| <div style='font-size:0.8rem; color:var(--text2); margin-bottom:8px'> | |
| 30q Trotter real-time evolution → stress propagation mapped to all 40 nodes. | |
| Dashed horizontal line separates direct VQE region (above, q0–q29) from mean-field extrapolated retail (below, q30–q39). | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if cascade_40 is not None and cascade_40.size > 0: | |
| n_snaps, n_casc = cascade_40.shape | |
| casc_labels = [f"{NODE_LABELS_40[i]} [T{TIER_MAP_40.get(i,3)}]" | |
| for i in range(min(n_casc, N_NODES))] | |
| fig_casc = go.Figure(go.Heatmap( | |
| z=cascade_40.T, | |
| x=[f"t={float(t):.1f}" for t in times_dyn[:n_snaps]], | |
| y=casc_labels, | |
| colorscale=[[0, '#064e3b'], [0.35, '#34d399'], [0.65, '#fbbf24'], [1, '#f87171']], | |
| zmin=0, zmax=1, | |
| colorbar=dict(title='Stress P(|1⟩)', thickness=12), | |
| )) | |
| # Tier boundary lines (horizontal) | |
| cumul = 0 | |
| for t in range(3): | |
| tc = sum(1 for i in range(min(n_casc, N_NODES)) if TIER_MAP_40.get(i) == t) | |
| cumul += tc | |
| fig_casc.add_hline(y=cumul - 0.5, line_color='#1a2d4a', line_width=1.5) | |
| # 30q/40q boundary | |
| vqe_boundary = N_VQE_Q | |
| fig_casc.add_hline(y=vqe_boundary - 0.5, line_color='#38bdf8', | |
| line_width=2, line_dash='dot') | |
| fig_casc.update_layout( | |
| **PD, height=max(380, n_casc * 12), | |
| xaxis=dict(title='Time snapshot'), | |
| yaxis=dict(autorange='reversed', tickfont=dict(size=9)), | |
| title=dict( | |
| text='Cascade propagation — yellow/red = increasing stress from RM-A shock · blue dashed = 30q/40q boundary', | |
| font=dict(size=12) | |
| ), | |
| ) | |
| st.plotly_chart(fig_casc, use_container_width=True) | |
| final_stress = cascade_40[-1] | |
| st.markdown(f""" | |
| <div class="alert-danger"> | |
| <strong style='color:var(--red)'>Final cascade state (t={float(times_dyn[-1]):.1f})</strong> — | |
| Mean stress across 40 nodes = <strong>{float(np.mean(final_stress)):.4f}</strong> · | |
| Nodes above 0.5 threshold = <strong>{int(np.sum(final_stress > 0.5))}/40</strong> · | |
| Worst node = <strong>{NODE_LABELS_40[int(np.argmax(final_stress))]}</strong> | |
| ({float(np.max(final_stress)):.4f}) | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 5 — QUBIT SCALING | |
| # ══════════════════════════════════════════════════════════════ | |
| elif page == "📈 Qubit Scaling": | |
| st.markdown('<div class="page-title">Qubit Scaling — Fujitsu A64FX Supercomputer</div>', unsafe_allow_html=True) | |
| st.markdown('<div class="page-sub">State-vector simulation: 12–30q measured · 40q Hamiltonian encoded · Exponential fit validates quantum advantage regime</div>', unsafe_allow_html=True) | |
| if not scl: | |
| st.error("QRSPPS_scaling_results.pkl not found.") | |
| st.stop() | |
| t40h = t_40q / 3600 | |
| # Header metrics | |
| m1, m2, m3, m4, m5, m6 = st.columns(6) | |
| m1.metric("Max qubits measured", f"{max(scl_ns) if scl_ns else 30}q", "Fujitsu A64FX MPI") | |
| m2.metric("30q state-vector", "17.2 GB", "node RAM ceiling") | |
| m3.metric("40q state-vector", "17,592 GB", "17.6 TB — impossible") | |
| m4.metric("40q eval time", f"{t40h:.0f} h", f"{t_40q:,.0f}s predicted") | |
| m5.metric("Exponential fit R²", f"{r_squared:.4f}", "near-perfect") | |
| m6.metric("Doubling rate", f"{doubling_rate:.4f}", "per qubit") | |
| st.markdown(f""" | |
| <div class="alert-danger" style='margin:12px 0'> | |
| <strong style='color:var(--red)'>40-Qubit Quantum Advantage Regime</strong> | |
| — | |
| <span style='font-size:0.88rem'> | |
| QR-SPPS Hamiltonian encodes a <strong>40-node supply chain</strong> in Hilbert space | |
| 2⁴⁰ = 1,099,511,627,776 states. | |
| State-vector simulation benchmarked to the physical node limit: | |
| <strong>30q = 17.2 GB (measured, MPI)</strong>. | |
| Exponential scaling: R² = <strong>{r_squared:.4f}</strong> over 6 MPI data points (24q–30q). | |
| Predicted 40q runtime: <strong>{t40h:.0f} hours per evaluation</strong> — | |
| classical state-vector is intractable. This is the quantum advantage regime. | |
| </span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| src_styles = { | |
| 'Single-node': dict(color='#38bdf8', symbol='circle'), | |
| 'MPI measured': dict(color='#34d399', symbol='square'), | |
| 'Extrapolated': dict(color='#fb923c', symbol='triangle-up'), | |
| } | |
| fig_rt = go.Figure() | |
| for src_type, style in src_styles.items(): | |
| idx = [i for i, s in enumerate(scl_srcs) if s == src_type] | |
| if idx: | |
| xs = [scl_ns[i] for i in idx] | |
| ys = [scl_times[i] for i in idx] | |
| fig_rt.add_trace(go.Scatter( | |
| x=xs, y=ys, mode='lines+markers', name=src_type, | |
| line=dict(color=style['color'], width=2.2, | |
| dash='dash' if src_type == 'Extrapolated' else 'solid'), | |
| marker=dict(color=style['color'], size=10, symbol=style['symbol']), | |
| )) | |
| # Exponential fit line | |
| if scl_ns: | |
| n_fit = list(np.linspace(min(scl_ns), 42, 200)) | |
| y_fit = [t_at_base * 2 ** (doubling_rate * (n - scl_ns[0])) for n in n_fit] | |
| fig_rt.add_trace(go.Scatter( | |
| x=n_fit, y=y_fit, mode='lines', | |
| name=f'O(2^n) fit R²={r_squared:.4f}', | |
| line=dict(color='#334155', dash='dot', width=1.5), | |
| )) | |
| # 30q marker | |
| t30_val = next((scl_times[i] for i, n in enumerate(scl_ns) if n == 30), None) | |
| if t30_val: | |
| fig_rt.add_trace(go.Scatter( | |
| x=[30], y=[t30_val], mode='markers', name='30q (QRSPPS exec)', | |
| marker=dict(color='#38bdf8', size=16, symbol='diamond', | |
| line=dict(color='white', width=2)), | |
| )) | |
| # 40q star | |
| fig_rt.add_trace(go.Scatter( | |
| x=[40], y=[t_40q], mode='markers', name=f'40q predicted ({t40h:.0f}h)', | |
| marker=dict(color='#f87171', size=18, symbol='star'), | |
| )) | |
| fig_rt.add_annotation( | |
| x=40, y=np.log10(t_40q) if t_40q > 0 else 6, | |
| text=f"40q<br>{t40h:.0f}h", showarrow=True, | |
| arrowhead=2, arrowcolor='#f87171', | |
| font=dict(color='#f87171', size=11, family='JetBrains Mono'), | |
| ax=-55, ay=-40, bgcolor='rgba(248,113,113,0.12)', | |
| ) | |
| # Vertical markers | |
| fig_rt.add_vline(x=30, line_color='#38bdf8', line_dash='dot', line_width=1.5, | |
| annotation_text='30q QRSPPS', annotation_font_color='#38bdf8') | |
| fig_rt.add_vline(x=40, line_color='#f87171', line_dash='dot', line_width=1, | |
| annotation_text='40q target', annotation_font_color='#f87171') | |
| fig_rt.update_layout( | |
| **PD, height=400, | |
| xaxis=dict(title='Number of qubits', range=[10, 43], gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Time per eval (s, log scale)', type='log', gridcolor='#1a2d4a'), | |
| title=dict( | |
| text=f'Runtime scaling — rate={doubling_rate:.4f}/q · R²={r_squared:.4f}', | |
| font=dict(size=12) | |
| ), | |
| ) | |
| st.plotly_chart(fig_rt, use_container_width=True) | |
| with c2: | |
| fig_mem = go.Figure() | |
| fig_mem.add_trace(go.Scatter( | |
| x=scl_ns, y=scl_mems, mode='lines+markers', name='State-vector RAM', | |
| line=dict(color='#a78bfa', width=2.2), | |
| marker=dict(color='#a78bfa', size=10), | |
| fill='tozeroy', fillcolor='rgba(167,139,250,0.07)', | |
| )) | |
| fig_mem.add_trace(go.Scatter( | |
| x=[40], y=[17592000], mode='markers', name='40q = 17.6 TB', | |
| marker=dict(color='#f87171', size=18, symbol='star'), | |
| )) | |
| if scl_ns: | |
| fig_mem.add_trace(go.Scatter( | |
| x=[scl_ns[-1], 40], y=[scl_mems[-1], 17592000], | |
| mode='lines', name='Extrapolated', | |
| line=dict(color='#f87171', dash='dash', width=1.5), | |
| )) | |
| fig_mem.add_hline(y=28900, line_color='#f87171', line_dash='dash', | |
| annotation_text='Node RAM limit 28.9 GB', | |
| annotation_font_color='#f87171') | |
| fig_mem.add_hline(y=17180, line_color='#fbbf24', line_dash='dash', | |
| annotation_text='30q = 17.2 GB (measured)', | |
| annotation_font_color='#fbbf24') | |
| fig_mem.add_annotation( | |
| x=40, y=4, | |
| text="40q = 17.6 TB<br>(impossible SV)", | |
| showarrow=True, arrowhead=2, arrowcolor='#f87171', | |
| font=dict(color='#f87171', size=10, family='JetBrains Mono'), | |
| ax=-65, ay=-35, bgcolor='rgba(248,113,113,0.12)', | |
| ) | |
| fig_mem.update_layout( | |
| **PD, height=400, | |
| xaxis=dict(title='Number of qubits', range=[10, 43], gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Memory (MB, log)', type='log', gridcolor='#1a2d4a'), | |
| title=dict(text='Memory scaling — 30q = node limit · 40q = 17.6 TB', font=dict(size=12)), | |
| ) | |
| st.plotly_chart(fig_mem, use_container_width=True) | |
| # Benchmark table | |
| st.markdown("#### Complete Benchmark Data — 12q to 40q (+ Extrapolated)") | |
| if scl_all: | |
| tbl = [] | |
| for r, src in zip(scl_all, scl_srcs): | |
| mem_mb = float(r.get('state_vec_mb', 0)) | |
| tbl.append({ | |
| 'Qubits': f"{r['n_qubits']}q", | |
| 'Time/eval': f"{r['mean_time']:.3f}s" if r['mean_time'] < 3600 else f"{r['mean_time']/3600:.1f}h", | |
| 'State-vector RAM': f"{mem_mb/1024:.2f} GB" if mem_mb > 1024 else f"{mem_mb:.1f} MB", | |
| 'Source': src, | |
| 'Hardware': 'Fujitsu A64FX MPI' if src == 'MPI measured' else ('Extrapolated' if src == 'Extrapolated' else 'A64FX single-node'), | |
| 'VQE Energy': f"{float(r.get('energy', 0)):.4f}" if r.get('energy') else 'N/A', | |
| }) | |
| tbl.append({ | |
| 'Qubits': '40q', | |
| 'Time/eval': f"{t40h:.0f}h ({t_40q:,.0f}s)", | |
| 'State-vector RAM': '17,592 GB (17.6 TB)', | |
| 'Source': f'Extrapolated R²={r_squared:.4f}', | |
| 'Hardware': 'Impossible — requires ~606 × A64FX nodes', | |
| 'VQE Energy': f'{vqe_e0_40:.4f} (encoded)', | |
| }) | |
| st.dataframe(pd.DataFrame(tbl), hide_index=True, use_container_width=True) | |
| # VQE convergence at 12q | |
| if hist_12: | |
| st.markdown("---") | |
| st.markdown("#### VQE Convergence at 12q — Benchmark Hamiltonian") | |
| fig_12 = go.Figure(go.Scatter( | |
| x=list(range(len(hist_12))), y=list(hist_12), | |
| mode='lines', line=dict(color='#38bdf8', width=2), | |
| fill='tozeroy', fillcolor='rgba(56,189,248,0.06)', | |
| )) | |
| fig_12.add_hline(y=float(hist_12[-1]), line_color='#34d399', line_dash='dash', | |
| annotation_text=f'E_final={float(hist_12[-1]):.4f}', | |
| annotation_font_color='#34d399') | |
| fig_12.update_layout( | |
| **PD, height=240, | |
| xaxis=dict(title='Iteration', gridcolor='#1a2d4a'), | |
| yaxis=dict(title='Energy', gridcolor='#1a2d4a'), | |
| title=dict(text='12q VQE convergence — supply-chain benchmark Hamiltonian', font=dict(size=12)), | |
| ) | |
| st.plotly_chart(fig_12, use_container_width=True) | |
| # ══════════════════════════════════════════════════════════════ | |
| # PAGE 6 — QARP FEEDBACK | |
| # ══════════════════════════════════════════════════════════════ | |
| elif page == "📋 QARP Feedback": | |
| st.markdown('<div class="page-title">Fujitsu QARP Usability Feedback</div>', unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="page-sub"> | |
| QR-SPPS Project · Fujitsu Quantum Simulator Challenge 2025-26 · | |
| Comprehensive feedback on QARP API, algorithms, and platform experience | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Overall score banner | |
| st.markdown(""" | |
| <div style='background:linear-gradient(135deg, rgba(56,189,248,0.08), rgba(52,211,153,0.06)); | |
| border:1px solid var(--border2); border-radius:14px; | |
| padding:20px 28px; margin-bottom:20px; | |
| display:flex; align-items:center; gap:28px'> | |
| <div style='text-align:center; min-width:90px'> | |
| <div style='font-family:Orbitron; font-size:2.4rem; font-weight:900; color:#38bdf8'>4.1</div> | |
| <div style='font-size:0.65rem; color:var(--muted); text-transform:uppercase; letter-spacing:0.12em'>Overall Score</div> | |
| <div style='color:#fbbf24; font-size:1.1rem; margin-top:2px'>★★★★☆</div> | |
| </div> | |
| <div style='flex:1'> | |
| <div style='font-weight:700; font-size:1.05rem; color:var(--text); margin-bottom:6px'> | |
| Fujitsu QARP: Production-Ready Algorithms · ARM Compatibility Needs Attention | |
| </div> | |
| <div style='font-size:0.84rem; color:var(--text2); line-height:1.7'> | |
| QARP's algorithm implementations (VQE, ADAPT-VQE gradient screening, DOS-QPE) are | |
| scientifically sound and enabled genuinely novel supply-chain quantum simulation. | |
| The primary obstacle is QulacsEngine incompatibility with A64FX ARM — | |
| once resolved via TketEngine(AerBackend), all algorithms performed excellently. | |
| The OpenFermion + QARP Hamiltonian pipeline mapped naturally to our Ising supply-chain model. | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Ratings grid | |
| st.markdown("### Component Ratings") | |
| ratings = [ | |
| ("QARP Installation & Setup", 5, "#34d399", | |
| "setup_env.sh worked cleanly on both login and compute nodes. pyenv + venv workflow is clean and reproducible. Requirements.txt complete."), | |
| ("QARP Documentation", 4, "#34d399", | |
| "mwe_vqe.py, mwe_adapt_vqe_vqd.py, mwe_dosqpe_algo.py are excellent. Missing: ARM-specific warnings and Jupyter/MPI incompatibility note."), | |
| ("VQE Algorithm", 5, "#34d399", | |
| "Clean API, COBYLA converged reliably on 30q supply-chain Hamiltonians (depth=3, 120 params, 5 restarts). E₀ = −44.6931 matches NB1 exact with zero error."), | |
| ("ADAPT-VQE Gradient Screening", 5, "#34d399", | |
| "Highly effective for policy ranking — ranked 6 interventions without full re-optimisation. Exactly the quantum efficiency gain needed for real-world policy applications."), | |
| ("DOS-QPE Survival Amplitude", 4, "#34d399", | |
| "Correct spectral reconstruction from mwe_dosqpe_algo.py pattern. 64 Trotter steps produced clean DOS. FFT + Hanning window pipeline worked directly."), | |
| ("OpenFermion Integration", 5, "#34d399", | |
| "QubitOperator → QARP Hamiltonian pipeline worked cleanly. ZZ + X Pauli encoding mapped naturally to Ising supply-chain structure with 57 supply edges."), | |
| ("TketEngine + AerBackend", 4, "#34d399", | |
| "Reliable QulacsEngine replacement. Worked consistently across all 4 notebooks once QulacsEngine segfault was diagnosed. Slightly slower than native Qulacs."), | |
| ("MPI / Distributed Support", 3, "#fbbf24", | |
| "mpi4py works correctly in sbatch scripts. Cannot be imported in Jupyter on compute nodes (OMPI not built with SLURM PMI). Needs better documentation."), | |
| ("QulacsEngine on A64FX ARM", 2, "#f87171", | |
| "Segfaults on ARM A64FX compute nodes — SIGSEGV at C extension level, uncatchable by Python try/except. Worked on x86 login node only. Required 3h to diagnose."), | |
| ("Error Messages & Diagnostics", 3, "#fbbf24", | |
| "Algorithm-level errors are clear. C-extension segfaults give no Python traceback. Recommending: QARP_DISABLE_MPI flag + ARM binary distribution."), | |
| ] | |
| col_a, col_b = st.columns(2) | |
| for i, (aspect, rating, color, comment) in enumerate(ratings): | |
| col = col_a if i % 2 == 0 else col_b | |
| with col: | |
| stars = "★" * rating + "☆" * (5 - rating) | |
| bar_w = int(rating / 5 * 100) | |
| st.markdown(f""" | |
| <div style='background:var(--surface); border:1px solid var(--border); | |
| border-left:3px solid {color}; border-radius:10px; | |
| padding:14px 16px; margin-bottom:10px'> | |
| <div style='display:flex; justify-content:space-between; align-items:center; margin-bottom:6px'> | |
| <span style='font-size:0.84rem; font-weight:700; color:var(--text)'>{aspect}</span> | |
| <span style='font-family:JetBrains Mono; color:{color}; font-size:0.88rem; white-space:nowrap'> | |
| {stars} {rating}/5 | |
| </span> | |
| </div> | |
| <div style='background:var(--bg); border-radius:4px; height:4px; margin-bottom:8px; overflow:hidden'> | |
| <div style='background:{color}; width:{bar_w}%; height:100%; border-radius:4px; | |
| transition:width 0.3s'></div> | |
| </div> | |
| <div style='color:var(--text2); font-size:0.76rem; line-height:1.5'>{comment}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("---") | |
| # Issues & positives | |
| issue_col, pos_col = st.columns(2) | |
| with issue_col: | |
| st.markdown("### 🔴 Issues Encountered") | |
| issues = [ | |
| ("#f87171", "CRITICAL", "QulacsEngine Segfault on A64FX", | |
| "QulacsEngine (.pyc) segfaults on ARM A64FX compute nodes — SIGSEGV at C extension level. Root cause: MPI_Init inside constructor; OMPI not built with SLURM PMIx.", | |
| "Replaced with direct qulacs Observable API + TketEngine(AerBackend). Took ~3h to diagnose.", | |
| "Distribute as .py source or provide ARM binary. Add QARP_DISABLE_MPI=1 to suppress C-level MPI init."), | |
| ("#f87171", "CRITICAL", "MPI Crashes Jupyter Kernel", | |
| "Importing mpi4py inside Jupyter on compute node causes immediate kernel crash: OPAL ERROR — OMPI not built with SLURM PMI support.", | |
| "All MPI code moved to sbatch scripts. Jupyter used for algorithm development only.", | |
| "Document this limitation prominently. Provide QARP_NO_MPI flag at C level."), | |
| ("#fbbf24", "HIGH", "Login vs Compute Node Architecture", | |
| "Login node is x86; compute nodes are ARM A64FX. Code that works on login node fails on compute nodes. Not documented.", | |
| "Learned through trial and error. All quantum code moved to compute nodes.", | |
| "Add prominent README warning: all quantum code must run on compute nodes only."), | |
| ("#fbbf24", "MEDIUM", "Interactive Partition 30-min Time Limit", | |
| "Insufficient for 28q+ benchmarks. 29q = 595s, 30q = 1192s per eval requires extended allocation.", | |
| "Used --time=12:00:00 for benchmark jobs.", | |
| "Provide 2–4h partition or document qubit limits per partition."), | |
| ] | |
| for color, sev, title, detail, fix, rec in issues: | |
| st.markdown(f""" | |
| <div style='background:var(--surface); border:1px solid var(--border); | |
| border-left:3px solid {color}; border-radius:10px; | |
| padding:14px 16px; margin-bottom:12px'> | |
| <div style='display:flex; align-items:center; gap:8px; margin-bottom:8px'> | |
| <span style='background:{color}22; color:{color}; border:1px solid {color}44; | |
| border-radius:4px; padding:2px 8px; font-size:0.67rem; font-weight:700; | |
| font-family:JetBrains Mono'>{sev}</span> | |
| <span style='font-weight:700; font-size:0.9rem; color:var(--text)'>{title}</span> | |
| </div> | |
| <div style='color:var(--text2); font-size:0.78rem; margin-bottom:6px; line-height:1.5'>{detail}</div> | |
| <div style='font-size:0.75rem; margin-bottom:3px'> | |
| <span style='color:var(--green); font-weight:600'>✓ Workaround:</span> | |
| <span style='color:var(--muted)'> {fix}</span> | |
| </div> | |
| <div style='font-size:0.75rem'> | |
| <span style='color:var(--accent); font-weight:600'>→ Recommendation:</span> | |
| <span style='color:var(--muted)'> {rec}</span> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with pos_col: | |
| st.markdown("### 🟢 What Worked Well") | |
| positives = [ | |
| ("QARP VQE API", | |
| f"Clean interface, COBYLA converged reliably on 30q supply-chain Hamiltonians. VQE reached E₀ = {vqe_e0_40:.4f} (40q scaled) with zero error vs exact diagonalisation."), | |
| ("ADAPT-VQE Gradient Screening", | |
| "Ranked 6 policy interventions (Rate hike, Supplier subsidy, Stockpile release, Trade diversion, Combined optimal) without full re-optimisation — exactly the quantum efficiency needed."), | |
| ("DOS-QPE Spectral Reconstruction", | |
| "64-step Trotter survival amplitude + Hanning FFT produced clean density of states. Pattern from mwe_dosqpe_algo.py was directly applicable to supply-chain Hamiltonian."), | |
| ("TketEngine + AerBackend Fallback", | |
| "Reliable QulacsEngine replacement. Worked consistently across all 4 notebooks once QulacsEngine was bypassed. Essential for A64FX ARM compatibility."), | |
| ("OpenFermion QubitOperator Integration", | |
| "ZZ + X Pauli encoding mapped naturally to Ising supply-chain structure. 57-edge supply network → Hamiltonian in < 10 lines of QARP code."), | |
| ("Example Scripts Quality", | |
| "mwe_vqe.py, mwe_adapt_vqe_vqd.py, mwe_dosqpe_algo.py: clear, well-commented, directly adaptable. Best part of the documentation package."), | |
| ("MPI Scaling Performance", | |
| "qulacs with MPI enabled scales correctly: 24q→30q measured on Fujitsu A64FX with R²=0.9948 exponential fit. 30q = 17.2 GB (measured) confirms performance claims."), | |
| ] | |
| for title, detail in positives: | |
| st.markdown(f""" | |
| <div style='background:rgba(52,211,153,0.04); border:1px solid rgba(52,211,153,0.15); | |
| border-left:3px solid var(--green); border-radius:10px; | |
| padding:12px 14px; margin-bottom:10px'> | |
| <div style='color:var(--green); font-weight:700; font-size:0.83rem; margin-bottom:4px'>{title}</div> | |
| <div style='color:var(--text2); font-size:0.78rem; line-height:1.55'>{detail}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### 📋 Priority Recommendations") | |
| recs = [ | |
| ("#f87171", "P1", "Fix QulacsEngine ARM A64FX", "Distribute as .py source or ARM binary. Showstopper for the competition platform."), | |
| ("#f87171", "P1", "Document Jupyter + MPI limitation", "Add clear note: mpi4py cannot be used in Jupyter on this cluster."), | |
| ("#fbbf24", "P2", "Architecture-specific setup guide", "Warn: login=x86, compute=ARM. All quantum code must run on compute nodes."), | |
| ("#fbbf24", "P2", "Extend Interactive partition time", "2–4h minimum for 28q+ workloads (currently 30min)."), | |
| ("#38bdf8", "P3", "QARP health-check script", "Verify all engines on current architecture before users spend hours debugging."), | |
| ("#38bdf8", "P3", "Progress callbacks for DOS-QPE", "Long Trotter evolutions need progress indicators."), | |
| ] | |
| for col, pri, title, detail in recs: | |
| st.markdown(f""" | |
| <div style='background:var(--surface); border:1px solid var(--border); | |
| border-left:3px solid {col}; border-radius:8px; | |
| padding:10px 14px; margin-bottom:8px'> | |
| <div style='display:flex; gap:8px; align-items:center; margin-bottom:3px'> | |
| <span style='color:{col}; font-size:0.67rem; font-weight:700; | |
| background:{col}22; padding:1px 6px; border-radius:3px; | |
| font-family:JetBrains Mono'>{pri}</span> | |
| <span style='color:var(--text); font-weight:600; font-size:0.82rem'>{title}</span> | |
| </div> | |
| <div style='color:var(--muted); font-size:0.75rem'>{detail}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Conclusion | |
| st.markdown("---") | |
| st.markdown(f""" | |
| <div style='background:var(--surface); border:1px solid var(--border2); | |
| border-radius:14px; padding:22px 28px'> | |
| <div style='font-family:Orbitron; font-weight:700; color:var(--accent); font-size:1rem; margin-bottom:10px'> | |
| ⚛ Conclusion | |
| </div> | |
| <div style='color:var(--text2); font-size:0.88rem; line-height:1.8'> | |
| Fujitsu QARP is a <strong style='color:var(--text)'>scientifically rigorous</strong> quantum algorithm library. | |
| VQE, ADAPT-VQE gradient screening, and DOS-QPE enabled genuine novel applications in supply-chain | |
| quantum risk simulation that would not be possible with classical methods. | |
| The ADAPT-VQE policy ranking was the standout feature — ranking 6 interventions | |
| by gradient without full re-optimisation is exactly the kind of quantum speedup | |
| that justifies real-world deployment. | |
| The primary obstacle — QulacsEngine incompatibility with A64FX ARM — is a single issue | |
| that, once resolved, would make QARP the definitive quantum algorithm library for | |
| the Fujitsu platform. The TketEngine fallback proved it is an engineering fix, not a | |
| fundamental limitation. | |
| </div> | |
| <div style='margin-top:14px; display:flex; gap:16px; flex-wrap:wrap'> | |
| <div style='font-family:JetBrains Mono; font-size:0.78rem; color:var(--green)'> | |
| ✓ Algorithm quality: 5/5 | |
| </div> | |
| <div style='font-family:JetBrains Mono; font-size:0.78rem; color:var(--green)'> | |
| ✓ API design: 4.5/5 | |
| </div> | |
| <div style='font-family:JetBrains Mono; font-size:0.78rem; color:var(--yellow)'> | |
| ⚠ ARM compatibility: 2/5 (fixable) | |
| </div> | |
| <div style='font-family:JetBrains Mono; font-size:0.78rem; color:var(--accent)'> | |
| Overall: 4.1/5 | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ── Footer ───────────────────────────────────────────────────── | |
| st.markdown(""" | |
| <div style='text-align:center; padding:28px 0 8px; border-top:1px solid #1a2d4a; margin-top:32px'> | |
| <div style='font-family:Orbitron; font-size:0.7rem; color:#1e3a5f; letter-spacing:0.2em'> | |
| QR-SPPS · FUJITSU QUANTUM SIMULATOR CHALLENGE 2025-26 · | |
| VQE · ADAPT-VQE · DOS-QPE | |
| </div> | |
| <div style='font-family:JetBrains Mono; font-size:0.65rem; color:#1a2d4a; margin-top:4px'> | |
| 40q encoded · 30q executed (17.2 GB MPI measured) · 40q extrapolated (17.6 TB, 1308h/eval) | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |