Upload app.py
Browse files
app.py
CHANGED
|
@@ -32,6 +32,7 @@ from quread.metrics import (
|
|
| 32 |
MetricThresholds,
|
| 33 |
)
|
| 34 |
from quread.layout_mapper import parse_layout_csv_text
|
|
|
|
| 35 |
|
| 36 |
# --- Qubit cap (configurable) ---
|
| 37 |
DEFAULT_MAX_QUBITS = 16 # safe default for CPU Spaces; change if you want
|
|
@@ -448,6 +449,55 @@ def _ideal_vs_noisy_plot(
|
|
| 448 |
return fig
|
| 449 |
|
| 450 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
# ---------- Styling ----------
|
| 452 |
CSS = """
|
| 453 |
#title h1 { font-size: 38px !important; margin-bottom: 4px; letter-spacing: -0.02em; }
|
|
@@ -686,6 +736,45 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 686 |
hotspot_detail_md = gr.Markdown()
|
| 687 |
hotspot_detail_plot = gr.Plot()
|
| 688 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
with gr.Tab("Explain"):
|
| 690 |
with gr.Group(elem_classes=["card"]):
|
| 691 |
gr.Markdown("<div class='section-title'>Explain (GPT-4o)</div>")
|
|
@@ -1079,6 +1168,88 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1079 |
)
|
| 1080 |
return _write_tmp("qubit_metrics.csv", to_metrics_csv(metrics))
|
| 1081 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
heat_btn.click(
|
| 1083 |
fn=_heat_and_hotspots_from_current,
|
| 1084 |
inputs=[
|
|
@@ -1121,6 +1292,26 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
|
|
| 1121 |
outputs=[metrics_csv_dl],
|
| 1122 |
)
|
| 1123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1124 |
synopsys_tcl_dl.click(
|
| 1125 |
fn=_dl_synopsys_tcl,
|
| 1126 |
inputs=[
|
|
|
|
| 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
|
|
|
|
| 449 |
return fig
|
| 450 |
|
| 451 |
|
| 452 |
+
def _metric_label(metric_key: str) -> str:
|
| 453 |
+
labels = {
|
| 454 |
+
"composite_risk": "Composite risk",
|
| 455 |
+
"gate_error": "Gate error",
|
| 456 |
+
"readout_error": "Readout error",
|
| 457 |
+
"decoherence_risk": "Decoherence risk",
|
| 458 |
+
"fidelity": "Fidelity",
|
| 459 |
+
"state_fidelity": "State fidelity",
|
| 460 |
+
"process_fidelity": "Process fidelity",
|
| 461 |
+
"coherence_health": "Coherence health",
|
| 462 |
+
}
|
| 463 |
+
return labels.get(metric_key, metric_key.replace("_", " ").title())
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
def _plot_metric_trends(series, labels, ranking_rows, metric_key, top_k):
|
| 467 |
+
if series.size == 0:
|
| 468 |
+
fig, ax = plt.subplots(figsize=(7, 3))
|
| 469 |
+
ax.set_title("No trend data")
|
| 470 |
+
ax.axis("off")
|
| 471 |
+
fig.tight_layout()
|
| 472 |
+
return fig
|
| 473 |
+
|
| 474 |
+
k = max(1, min(int(top_k), len(ranking_rows)))
|
| 475 |
+
selected_qubits = [int(ranking_rows[i]["qubit"]) for i in range(k)]
|
| 476 |
+
x = np.arange(series.shape[0])
|
| 477 |
+
fig, ax = plt.subplots(figsize=(8, 4))
|
| 478 |
+
for q in selected_qubits:
|
| 479 |
+
ax.plot(x, series[:, q], marker="o", linewidth=2, label=f"q{q}")
|
| 480 |
+
|
| 481 |
+
xticks = labels
|
| 482 |
+
if len(xticks) > 12:
|
| 483 |
+
step = max(1, len(xticks) // 10)
|
| 484 |
+
keep = [idx for idx in range(len(xticks)) if idx % step == 0 or idx == len(xticks) - 1]
|
| 485 |
+
ax.set_xticks(keep)
|
| 486 |
+
ax.set_xticklabels([xticks[i] for i in keep], rotation=35, ha="right")
|
| 487 |
+
else:
|
| 488 |
+
ax.set_xticks(x)
|
| 489 |
+
ax.set_xticklabels(xticks, rotation=35, ha="right")
|
| 490 |
+
|
| 491 |
+
ax.set_ylim(0.0, 1.0)
|
| 492 |
+
ax.set_ylabel(_metric_label(metric_key))
|
| 493 |
+
ax.set_xlabel("Snapshot")
|
| 494 |
+
ax.set_title(f"Temporal drift: top {k} qubits by latest {_metric_label(metric_key).lower()}")
|
| 495 |
+
ax.grid(alpha=0.2, linestyle="--")
|
| 496 |
+
ax.legend(ncols=2, fontsize=8)
|
| 497 |
+
fig.tight_layout()
|
| 498 |
+
return fig
|
| 499 |
+
|
| 500 |
+
|
| 501 |
# ---------- Styling ----------
|
| 502 |
CSS = """
|
| 503 |
#title h1 { font-size: 38px !important; margin-bottom: 4px; letter-spacing: -0.02em; }
|
|
|
|
| 736 |
hotspot_detail_md = gr.Markdown()
|
| 737 |
hotspot_detail_plot = gr.Plot()
|
| 738 |
|
| 739 |
+
with gr.Group(elem_classes=["card"]):
|
| 740 |
+
gr.Markdown("<div class='section-title'>Temporal Drift (Calibration Snapshots)</div>")
|
| 741 |
+
with gr.Row():
|
| 742 |
+
trend_metric = gr.Dropdown(
|
| 743 |
+
choices=[
|
| 744 |
+
"composite_risk",
|
| 745 |
+
"gate_error",
|
| 746 |
+
"readout_error",
|
| 747 |
+
"decoherence_risk",
|
| 748 |
+
"fidelity",
|
| 749 |
+
"state_fidelity",
|
| 750 |
+
"process_fidelity",
|
| 751 |
+
"coherence_health",
|
| 752 |
+
],
|
| 753 |
+
value="composite_risk",
|
| 754 |
+
label="Trend metric",
|
| 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)",
|
| 761 |
+
file_types=[".json", ".jsonl", ".txt"],
|
| 762 |
+
type="filepath",
|
| 763 |
+
)
|
| 764 |
+
trend_snapshots_text = gr.Textbox(
|
| 765 |
+
lines=8,
|
| 766 |
+
label="Snapshots JSON/JSONL (optional)",
|
| 767 |
+
placeholder='[{"timestamp":"2026-02-12","qubits":{"0":{"gate_error":0.012,"readout_error":0.02,"t1_us":82,"t2_us":61,"fidelity":0.991}}}]',
|
| 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 selected metric first)",
|
| 776 |
+
)
|
| 777 |
+
|
| 778 |
with gr.Tab("Explain"):
|
| 779 |
with gr.Group(elem_classes=["card"]):
|
| 780 |
gr.Markdown("<div class='section-title'>Explain (GPT-4o)</div>")
|
|
|
|
| 1168 |
)
|
| 1169 |
return _write_tmp("qubit_metrics.csv", to_metrics_csv(metrics))
|
| 1170 |
|
| 1171 |
+
def _trend_from_snapshots(
|
| 1172 |
+
qc,
|
| 1173 |
+
n_qubits,
|
| 1174 |
+
snapshots_file,
|
| 1175 |
+
snapshots_text,
|
| 1176 |
+
trend_metric_value,
|
| 1177 |
+
trend_top_qubits,
|
| 1178 |
+
activity_w,
|
| 1179 |
+
gate_error_w,
|
| 1180 |
+
readout_error_w,
|
| 1181 |
+
decoherence_w,
|
| 1182 |
+
fidelity_w,
|
| 1183 |
+
warning_thr,
|
| 1184 |
+
critical_thr,
|
| 1185 |
+
):
|
| 1186 |
+
text = _read_uploaded_text(snapshots_file).strip()
|
| 1187 |
+
if not text:
|
| 1188 |
+
text = str(snapshots_text or "").strip()
|
| 1189 |
+
|
| 1190 |
+
if not text:
|
| 1191 |
+
fig, ax = plt.subplots(figsize=(7, 3))
|
| 1192 |
+
ax.set_title("Temporal drift")
|
| 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 fig, "No snapshots provided.", []
|
| 1197 |
+
|
| 1198 |
+
csv_text = to_csv(qc.history)
|
| 1199 |
+
weights, thresholds = _metric_controls_to_models(
|
| 1200 |
+
activity_w,
|
| 1201 |
+
gate_error_w,
|
| 1202 |
+
readout_error_w,
|
| 1203 |
+
decoherence_w,
|
| 1204 |
+
fidelity_w,
|
| 1205 |
+
warning_thr,
|
| 1206 |
+
critical_thr,
|
| 1207 |
+
)
|
| 1208 |
+
|
| 1209 |
+
try:
|
| 1210 |
+
series, labels, ranking, meta = compute_metric_trends(
|
| 1211 |
+
csv_text,
|
| 1212 |
+
int(n_qubits),
|
| 1213 |
+
text,
|
| 1214 |
+
metric=str(trend_metric_value),
|
| 1215 |
+
state_vector=np.asarray(qc.state, dtype=complex),
|
| 1216 |
+
weights=weights,
|
| 1217 |
+
thresholds=thresholds,
|
| 1218 |
+
)
|
| 1219 |
+
except Exception as exc:
|
| 1220 |
+
fig, ax = plt.subplots(figsize=(7, 3))
|
| 1221 |
+
ax.set_title("Temporal drift")
|
| 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 fig, f"Drift analysis failed: {exc}", []
|
| 1226 |
+
|
| 1227 |
+
fig = _plot_metric_trends(
|
| 1228 |
+
series,
|
| 1229 |
+
labels,
|
| 1230 |
+
ranking,
|
| 1231 |
+
str(meta.get("metric", trend_metric_value)),
|
| 1232 |
+
int(trend_top_qubits),
|
| 1233 |
+
)
|
| 1234 |
+
table_rows = []
|
| 1235 |
+
for row in ranking:
|
| 1236 |
+
table_rows.append(
|
| 1237 |
+
[
|
| 1238 |
+
int(row["qubit"]),
|
| 1239 |
+
round(float(row["latest"]), 6),
|
| 1240 |
+
round(float(row["baseline"]), 6),
|
| 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,
|
| 1255 |
inputs=[
|
|
|
|
| 1292 |
outputs=[metrics_csv_dl],
|
| 1293 |
)
|
| 1294 |
|
| 1295 |
+
trend_btn.click(
|
| 1296 |
+
fn=_trend_from_snapshots,
|
| 1297 |
+
inputs=[
|
| 1298 |
+
qc_state,
|
| 1299 |
+
n_qubits,
|
| 1300 |
+
trend_snapshots_file,
|
| 1301 |
+
trend_snapshots_text,
|
| 1302 |
+
trend_metric,
|
| 1303 |
+
trend_top_k,
|
| 1304 |
+
w_activity,
|
| 1305 |
+
w_gate,
|
| 1306 |
+
w_readout,
|
| 1307 |
+
w_decoherence,
|
| 1308 |
+
w_fidelity,
|
| 1309 |
+
thr_warning,
|
| 1310 |
+
thr_critical,
|
| 1311 |
+
],
|
| 1312 |
+
outputs=[trend_plot, trend_status, trend_table],
|
| 1313 |
+
)
|
| 1314 |
+
|
| 1315 |
synopsys_tcl_dl.click(
|
| 1316 |
fn=_dl_synopsys_tcl,
|
| 1317 |
inputs=[
|