Upload app.py
Browse files
app.py
CHANGED
|
@@ -32,7 +32,7 @@ from quread.metrics import (
|
|
| 32 |
MetricThresholds,
|
| 33 |
)
|
| 34 |
from quread.layout_mapper import parse_layout_csv_text
|
| 35 |
-
from quread.trends import compute_metric_trends
|
| 36 |
|
| 37 |
# --- Qubit cap (configurable) ---
|
| 38 |
DEFAULT_MAX_QUBITS = 16 # safe default for CPU Spaces; change if you want
|
|
@@ -498,6 +498,59 @@ def _plot_metric_trends(series, labels, ranking_rows, metric_key, top_k):
|
|
| 498 |
return fig
|
| 499 |
|
| 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
# ---------- Styling ----------
|
| 502 |
CSS = """
|
| 503 |
#title h1 { font-size: 38px !important; margin-bottom: 4px; letter-spacing: -0.02em; }
|
|
@@ -540,6 +593,7 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 540 |
selected_gate_state = gr.State()
|
| 541 |
explanation_md = gr.State("")
|
| 542 |
last_explained_hash = gr.State("")
|
|
|
|
| 543 |
|
| 544 |
with gr.Row(elem_classes=["app-shell"]):
|
| 545 |
# Sidebar
|
|
@@ -755,6 +809,12 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 755 |
)
|
| 756 |
trend_top_k = gr.Slider(1, 32, value=8, step=1, label="Trend lines (top qubits)")
|
| 757 |
trend_btn = gr.Button("Analyze drift", variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
with gr.Accordion("Snapshot Input", open=False):
|
| 759 |
trend_snapshots_file = gr.File(
|
| 760 |
label="Snapshots file (.json/.jsonl/.txt)",
|
|
@@ -768,12 +828,20 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 768 |
)
|
| 769 |
gr.Markdown("<div class='small-note'>If both are provided, file input is used.</div>")
|
| 770 |
trend_status = gr.Markdown("Upload snapshots and click Analyze drift.")
|
|
|
|
| 771 |
trend_plot = gr.Plot()
|
| 772 |
trend_table = gr.Dataframe(
|
| 773 |
headers=["qubit", "latest", "baseline", "delta"],
|
| 774 |
interactive=False,
|
| 775 |
-
label="Latest ranking (highest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
)
|
|
|
|
| 777 |
|
| 778 |
with gr.Tab("Explain"):
|
| 779 |
with gr.Group(elem_classes=["card"]):
|
|
@@ -1175,6 +1243,11 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1175 |
snapshots_text,
|
| 1176 |
trend_metric_value,
|
| 1177 |
trend_top_qubits,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1178 |
activity_w,
|
| 1179 |
gate_error_w,
|
| 1180 |
readout_error_w,
|
|
@@ -1193,7 +1266,15 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1193 |
ax.text(0.5, 0.5, "Provide calibration snapshots (JSON/JSONL).", ha="center", va="center")
|
| 1194 |
ax.axis("off")
|
| 1195 |
fig.tight_layout()
|
| 1196 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1197 |
|
| 1198 |
csv_text = to_csv(qc.history)
|
| 1199 |
weights, thresholds = _metric_controls_to_models(
|
|
@@ -1222,7 +1303,15 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1222 |
ax.text(0.5, 0.5, f"Unable to parse snapshots: {exc}", ha="center", va="center")
|
| 1223 |
ax.axis("off")
|
| 1224 |
fig.tight_layout()
|
| 1225 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
|
| 1227 |
fig = _plot_metric_trends(
|
| 1228 |
series,
|
|
@@ -1241,14 +1330,47 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1241 |
round(float(row["delta"]), 6),
|
| 1242 |
]
|
| 1243 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1244 |
status = (
|
| 1245 |
f"Snapshots parsed: {int(meta.get('parsed', 0))}"
|
| 1246 |
f" | Skipped: {int(meta.get('skipped', 0))}"
|
| 1247 |
f" | Format: {meta.get('format', 'unknown')}"
|
| 1248 |
f" | Points: {int(meta.get('points', 0))}"
|
| 1249 |
f" | Metric: {meta.get('metric', trend_metric_value)}"
|
|
|
|
| 1250 |
)
|
| 1251 |
-
return fig, status, table_rows
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1252 |
|
| 1253 |
heat_btn.click(
|
| 1254 |
fn=_heat_and_hotspots_from_current,
|
|
@@ -1301,6 +1423,11 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1301 |
trend_snapshots_text,
|
| 1302 |
trend_metric,
|
| 1303 |
trend_top_k,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1304 |
w_activity,
|
| 1305 |
w_gate,
|
| 1306 |
w_readout,
|
|
@@ -1309,7 +1436,21 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1309 |
thr_warning,
|
| 1310 |
thr_critical,
|
| 1311 |
],
|
| 1312 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1313 |
)
|
| 1314 |
|
| 1315 |
synopsys_tcl_dl.click(
|
|
|
|
| 32 |
MetricThresholds,
|
| 33 |
)
|
| 34 |
from quread.layout_mapper import parse_layout_csv_text
|
| 35 |
+
from quread.trends import compute_metric_trends, compute_drift_alerts, alerts_to_csv
|
| 36 |
|
| 37 |
# --- Qubit cap (configurable) ---
|
| 38 |
DEFAULT_MAX_QUBITS = 16 # safe default for CPU Spaces; change if you want
|
|
|
|
| 498 |
return fig
|
| 499 |
|
| 500 |
|
| 501 |
+
def _drift_alert_summary(alert_rows):
|
| 502 |
+
total = len(alert_rows)
|
| 503 |
+
critical = sum(1 for r in alert_rows if str(r.get("level")) == "critical")
|
| 504 |
+
warning = sum(1 for r in alert_rows if str(r.get("level")) == "warning")
|
| 505 |
+
ok = max(0, total - critical - warning)
|
| 506 |
+
if critical > 0:
|
| 507 |
+
badge = "critical drift detected"
|
| 508 |
+
elif warning > 0:
|
| 509 |
+
badge = "warning drift detected"
|
| 510 |
+
else:
|
| 511 |
+
badge = "stable"
|
| 512 |
+
return f"Auto-flag summary: critical={critical}, warning={warning}, ok={ok} ({badge})."
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def _mitigation_hint_for_metric(metric_key, level):
|
| 516 |
+
severity = str(level or "ok")
|
| 517 |
+
if metric_key == "gate_error":
|
| 518 |
+
return "Prioritize gate pulse recalibration and schedule RB/IRB validation."
|
| 519 |
+
if metric_key == "readout_error":
|
| 520 |
+
return "Run readout discriminator retuning and measurement calibration refresh."
|
| 521 |
+
if metric_key == "decoherence_risk":
|
| 522 |
+
return "Reduce circuit depth on flagged qubits and revisit idle decoupling timing."
|
| 523 |
+
if metric_key in {"fidelity", "state_fidelity", "process_fidelity"}:
|
| 524 |
+
return "Re-characterize fidelity with tomography/benchmarking and re-tune control stack."
|
| 525 |
+
if metric_key == "coherence_health":
|
| 526 |
+
return "Investigate T1/T2 drift, thermal environment, and schedule recalibration windows."
|
| 527 |
+
if severity == "critical":
|
| 528 |
+
return "Lock flagged qubits out of high-depth paths and escalate full calibration."
|
| 529 |
+
if severity == "warning":
|
| 530 |
+
return "Increase monitoring cadence and rebalance workloads away from flagged qubits."
|
| 531 |
+
return "No immediate action required; continue scheduled monitoring."
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
def _drift_recommendations_markdown(alert_rows, metric_key, max_items=6):
|
| 535 |
+
if not alert_rows:
|
| 536 |
+
return "No recommendations available yet. Run drift analysis first."
|
| 537 |
+
|
| 538 |
+
actionable = [r for r in alert_rows if str(r.get("level")) in {"critical", "warning"}]
|
| 539 |
+
if not actionable:
|
| 540 |
+
return "### Recommended Actions\n- No active drift alerts. Maintain regular calibration cadence."
|
| 541 |
+
|
| 542 |
+
lines = ["### Recommended Actions"]
|
| 543 |
+
k = max(1, min(int(max_items), len(actionable)))
|
| 544 |
+
for row in actionable[:k]:
|
| 545 |
+
q = int(row.get("qubit", -1))
|
| 546 |
+
level = str(row.get("level", "warning"))
|
| 547 |
+
hint = _mitigation_hint_for_metric(str(metric_key), level)
|
| 548 |
+
triggers = row.get("triggers") or []
|
| 549 |
+
reason = ", ".join(str(t) for t in triggers[:2]) if triggers else "trend threshold crossing"
|
| 550 |
+
lines.append(f"- q{q} ({level}): {hint} Trigger: {reason}.")
|
| 551 |
+
return "\n".join(lines)
|
| 552 |
+
|
| 553 |
+
|
| 554 |
# ---------- Styling ----------
|
| 555 |
CSS = """
|
| 556 |
#title h1 { font-size: 38px !important; margin-bottom: 4px; letter-spacing: -0.02em; }
|
|
|
|
| 593 |
selected_gate_state = gr.State()
|
| 594 |
explanation_md = gr.State("")
|
| 595 |
last_explained_hash = gr.State("")
|
| 596 |
+
drift_alert_rows_state = gr.State([])
|
| 597 |
|
| 598 |
with gr.Row(elem_classes=["app-shell"]):
|
| 599 |
# Sidebar
|
|
|
|
| 809 |
)
|
| 810 |
trend_top_k = gr.Slider(1, 32, value=8, step=1, label="Trend lines (top qubits)")
|
| 811 |
trend_btn = gr.Button("Analyze drift", variant="secondary")
|
| 812 |
+
with gr.Row():
|
| 813 |
+
drift_delta_warning = gr.Slider(0.0, 1.0, value=0.08, step=0.01, label="Drift delta warning")
|
| 814 |
+
drift_delta_critical = gr.Slider(0.0, 1.0, value=0.18, step=0.01, label="Drift delta critical")
|
| 815 |
+
drift_slope_warning = gr.Slider(0.0, 0.5, value=0.02, step=0.005, label="Drift slope warning")
|
| 816 |
+
drift_slope_critical = gr.Slider(0.0, 0.5, value=0.05, step=0.005, label="Drift slope critical")
|
| 817 |
+
drift_alert_top_k = gr.Slider(1, 64, value=16, step=1, label="Auto-flag rows")
|
| 818 |
with gr.Accordion("Snapshot Input", open=False):
|
| 819 |
trend_snapshots_file = gr.File(
|
| 820 |
label="Snapshots file (.json/.jsonl/.txt)",
|
|
|
|
| 828 |
)
|
| 829 |
gr.Markdown("<div class='small-note'>If both are provided, file input is used.</div>")
|
| 830 |
trend_status = gr.Markdown("Upload snapshots and click Analyze drift.")
|
| 831 |
+
drift_alert_csv_dl = gr.DownloadButton("Download auto-flag CSV")
|
| 832 |
trend_plot = gr.Plot()
|
| 833 |
trend_table = gr.Dataframe(
|
| 834 |
headers=["qubit", "latest", "baseline", "delta"],
|
| 835 |
interactive=False,
|
| 836 |
+
label="Latest ranking (highest metric risk first)",
|
| 837 |
+
)
|
| 838 |
+
drift_alert_status = gr.Markdown("Auto-flag summary: awaiting trend analysis.")
|
| 839 |
+
drift_alert_table = gr.Dataframe(
|
| 840 |
+
headers=["qubit", "alert", "latest_risk", "risk_delta", "risk_slope", "triggers"],
|
| 841 |
+
interactive=False,
|
| 842 |
+
label="Auto-flag panel",
|
| 843 |
)
|
| 844 |
+
drift_reco_md = gr.Markdown("Recommendations appear after drift analysis.")
|
| 845 |
|
| 846 |
with gr.Tab("Explain"):
|
| 847 |
with gr.Group(elem_classes=["card"]):
|
|
|
|
| 1243 |
snapshots_text,
|
| 1244 |
trend_metric_value,
|
| 1245 |
trend_top_qubits,
|
| 1246 |
+
drift_delta_warn,
|
| 1247 |
+
drift_delta_crit,
|
| 1248 |
+
drift_slope_warn,
|
| 1249 |
+
drift_slope_crit,
|
| 1250 |
+
drift_alert_rows_max,
|
| 1251 |
activity_w,
|
| 1252 |
gate_error_w,
|
| 1253 |
readout_error_w,
|
|
|
|
| 1266 |
ax.text(0.5, 0.5, "Provide calibration snapshots (JSON/JSONL).", ha="center", va="center")
|
| 1267 |
ax.axis("off")
|
| 1268 |
fig.tight_layout()
|
| 1269 |
+
return (
|
| 1270 |
+
fig,
|
| 1271 |
+
"No snapshots provided.",
|
| 1272 |
+
[],
|
| 1273 |
+
"Auto-flag summary: no data.",
|
| 1274 |
+
[],
|
| 1275 |
+
[],
|
| 1276 |
+
"No recommendations available yet. Run drift analysis first.",
|
| 1277 |
+
)
|
| 1278 |
|
| 1279 |
csv_text = to_csv(qc.history)
|
| 1280 |
weights, thresholds = _metric_controls_to_models(
|
|
|
|
| 1303 |
ax.text(0.5, 0.5, f"Unable to parse snapshots: {exc}", ha="center", va="center")
|
| 1304 |
ax.axis("off")
|
| 1305 |
fig.tight_layout()
|
| 1306 |
+
return (
|
| 1307 |
+
fig,
|
| 1308 |
+
f"Drift analysis failed: {exc}",
|
| 1309 |
+
[],
|
| 1310 |
+
f"Auto-flag summary unavailable: {exc}",
|
| 1311 |
+
[],
|
| 1312 |
+
[],
|
| 1313 |
+
f"Recommendation generation unavailable: {exc}",
|
| 1314 |
+
)
|
| 1315 |
|
| 1316 |
fig = _plot_metric_trends(
|
| 1317 |
series,
|
|
|
|
| 1330 |
round(float(row["delta"]), 6),
|
| 1331 |
]
|
| 1332 |
)
|
| 1333 |
+
alerts = compute_drift_alerts(
|
| 1334 |
+
ranking,
|
| 1335 |
+
warning_threshold=float(warning_thr),
|
| 1336 |
+
critical_threshold=float(critical_thr),
|
| 1337 |
+
delta_warning=float(drift_delta_warn),
|
| 1338 |
+
delta_critical=float(drift_delta_crit),
|
| 1339 |
+
slope_warning=float(drift_slope_warn),
|
| 1340 |
+
slope_critical=float(drift_slope_crit),
|
| 1341 |
+
)
|
| 1342 |
+
max_rows = max(1, min(int(drift_alert_rows_max), len(alerts)))
|
| 1343 |
+
alert_table_rows = []
|
| 1344 |
+
for row in alerts[:max_rows]:
|
| 1345 |
+
alert_table_rows.append(
|
| 1346 |
+
[
|
| 1347 |
+
int(row["qubit"]),
|
| 1348 |
+
str(row["level"]),
|
| 1349 |
+
round(float(row["latest_risk"]), 6),
|
| 1350 |
+
round(float(row["risk_delta"]), 6),
|
| 1351 |
+
round(float(row["risk_slope"]), 6),
|
| 1352 |
+
"; ".join(row["triggers"]) if row["triggers"] else "-",
|
| 1353 |
+
]
|
| 1354 |
+
)
|
| 1355 |
+
alert_status = _drift_alert_summary(alerts)
|
| 1356 |
+
recommendation_md = _drift_recommendations_markdown(
|
| 1357 |
+
alerts,
|
| 1358 |
+
str(meta.get("metric", trend_metric_value)),
|
| 1359 |
+
max_items=6,
|
| 1360 |
+
)
|
| 1361 |
status = (
|
| 1362 |
f"Snapshots parsed: {int(meta.get('parsed', 0))}"
|
| 1363 |
f" | Skipped: {int(meta.get('skipped', 0))}"
|
| 1364 |
f" | Format: {meta.get('format', 'unknown')}"
|
| 1365 |
f" | Points: {int(meta.get('points', 0))}"
|
| 1366 |
f" | Metric: {meta.get('metric', trend_metric_value)}"
|
| 1367 |
+
f" | Risk mode: {meta.get('risk_mode', 'unknown')}"
|
| 1368 |
)
|
| 1369 |
+
return fig, status, table_rows, alert_status, alert_table_rows, alerts, recommendation_md
|
| 1370 |
+
|
| 1371 |
+
def _dl_drift_alert_csv(alert_rows):
|
| 1372 |
+
rows = alert_rows or []
|
| 1373 |
+
return _write_tmp("drift_alerts.csv", alerts_to_csv(rows))
|
| 1374 |
|
| 1375 |
heat_btn.click(
|
| 1376 |
fn=_heat_and_hotspots_from_current,
|
|
|
|
| 1423 |
trend_snapshots_text,
|
| 1424 |
trend_metric,
|
| 1425 |
trend_top_k,
|
| 1426 |
+
drift_delta_warning,
|
| 1427 |
+
drift_delta_critical,
|
| 1428 |
+
drift_slope_warning,
|
| 1429 |
+
drift_slope_critical,
|
| 1430 |
+
drift_alert_top_k,
|
| 1431 |
w_activity,
|
| 1432 |
w_gate,
|
| 1433 |
w_readout,
|
|
|
|
| 1436 |
thr_warning,
|
| 1437 |
thr_critical,
|
| 1438 |
],
|
| 1439 |
+
outputs=[
|
| 1440 |
+
trend_plot,
|
| 1441 |
+
trend_status,
|
| 1442 |
+
trend_table,
|
| 1443 |
+
drift_alert_status,
|
| 1444 |
+
drift_alert_table,
|
| 1445 |
+
drift_alert_rows_state,
|
| 1446 |
+
drift_reco_md,
|
| 1447 |
+
],
|
| 1448 |
+
)
|
| 1449 |
+
|
| 1450 |
+
drift_alert_csv_dl.click(
|
| 1451 |
+
fn=_dl_drift_alert_csv,
|
| 1452 |
+
inputs=[drift_alert_rows_state],
|
| 1453 |
+
outputs=[drift_alert_csv_dl],
|
| 1454 |
)
|
| 1455 |
|
| 1456 |
synopsys_tcl_dl.click(
|