revert: rollback to working version with disclaimer
Browse files
app.py
CHANGED
|
@@ -384,17 +384,27 @@ def _final_payload(
|
|
| 384 |
}
|
| 385 |
|
| 386 |
|
| 387 |
-
def verify_on_ibm(label: str, mode: str) -> dict[str, Any]:
|
| 388 |
-
"""Submit the example to IBM and
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
if label not in EXAMPLES:
|
| 390 |
-
|
|
|
|
| 391 |
try:
|
| 392 |
narrow_mode = _coerce_mode(mode)
|
| 393 |
except ValueError as exc:
|
| 394 |
-
|
|
|
|
| 395 |
|
| 396 |
if not IBM_AVAILABLE:
|
| 397 |
-
|
| 398 |
"status": "error",
|
| 399 |
"error": (
|
| 400 |
"IBM_QUANTUM_TOKEN and/or IBM_QUANTUM_INSTANCE are not set "
|
|
@@ -402,34 +412,125 @@ def verify_on_ibm(label: str, mode: str) -> dict[str, Any]:
|
|
| 402 |
"unavailable; use the simulator button."
|
| 403 |
),
|
| 404 |
}
|
|
|
|
| 405 |
|
| 406 |
start = time.monotonic()
|
| 407 |
try:
|
| 408 |
job, job_id, backend_name, prepared = _prepare_and_submit(label)
|
| 409 |
except Exception as exc:
|
| 410 |
-
|
| 411 |
"status": "error",
|
| 412 |
"error": f"{type(exc).__name__}: {exc}",
|
| 413 |
}
|
|
|
|
| 414 |
|
| 415 |
-
|
| 416 |
-
return {
|
| 417 |
"status": "submitted",
|
| 418 |
"ibm_job_id": job_id,
|
| 419 |
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 420 |
"ibm_status": "QUEUED",
|
| 421 |
-
"seconds_elapsed":
|
| 422 |
"backend": backend_name,
|
| 423 |
"example": label,
|
| 424 |
"mode": narrow_mode,
|
| 425 |
"message": (
|
| 426 |
-
|
| 427 |
-
"
|
| 428 |
-
"
|
| 429 |
-
"
|
| 430 |
-
|
|
|
|
| 431 |
),
|
| 432 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
if not IBM_AVAILABLE:
|
| 434 |
return {
|
| 435 |
"error": (
|
|
@@ -580,12 +681,10 @@ with gr.Blocks(title="QVerify") as demo:
|
|
| 580 |
interactive=IBM_AVAILABLE,
|
| 581 |
)
|
| 582 |
gr.Markdown(_ibm_status_md)
|
| 583 |
-
gr.Markdown("⚠️ **Known limitation:** On HuggingFace's free tier (CPU Basic), the live progress stream may disconnect during long IBM jobs (typically 1-3 minutes). The job **continues executing on IBM hardware** regardless. If the Result panel clears before completion, copy the Job ID shown initially and use the \"Recover a previous job\" panel below. All executions are verifiable at [IBM Quantum Workloads](https://quantum.cloud.ibm.com/workloads).")
|
| 584 |
|
| 585 |
with gr.Column(scale=1):
|
| 586 |
gr.Markdown("### Result")
|
| 587 |
output = gr.JSON(label="Verification result")
|
| 588 |
-
btn_check_status = gr.Button("Check job status", variant="secondary", visible=True)
|
| 589 |
|
| 590 |
example_dd.change(
|
| 591 |
_on_example_change,
|
|
@@ -602,11 +701,6 @@ with gr.Blocks(title="QVerify") as demo:
|
|
| 602 |
inputs=[example_dd, mode_radio],
|
| 603 |
outputs=output,
|
| 604 |
)
|
| 605 |
-
btn_check_status.click(
|
| 606 |
-
lambda out: check_job_status(out.get("ibm_job_id", "")) if isinstance(out, dict) else {"error": "No job submitted yet"},
|
| 607 |
-
inputs=output,
|
| 608 |
-
outputs=output,
|
| 609 |
-
)
|
| 610 |
|
| 611 |
gr.Markdown("---")
|
| 612 |
gr.Markdown("### 4. Recover a previous job (fallback)")
|
|
@@ -640,15 +734,14 @@ with gr.Blocks(title="QVerify") as demo:
|
|
| 640 |
"host the translator, so this Space is verifier-only. See the "
|
| 641 |
"[GitHub repository](https://github.com/Quantum-Labor/qverify) "
|
| 642 |
"for the end-to-end pipeline.\n\n"
|
| 643 |
-
"**IBM hardware runs
|
| 644 |
-
"
|
| 645 |
-
"
|
| 646 |
-
"
|
| 647 |
-
"
|
| 648 |
-
|
| 649 |
-
"
|
| 650 |
-
"
|
| 651 |
-
"[here](https://github.com/Quantum-Labor/qverify#hardware-run)."
|
| 652 |
)
|
| 653 |
|
| 654 |
|
|
|
|
| 384 |
}
|
| 385 |
|
| 386 |
|
| 387 |
+
def verify_on_ibm(label: str, mode: str) -> Iterator[dict[str, Any]]:
|
| 388 |
+
"""Submit the example to IBM and yield progressive status updates.
|
| 389 |
+
|
| 390 |
+
First yield: ``status="submitted"`` with the IBM job_id and dashboard
|
| 391 |
+
URL. Subsequent yields: ``status="in_progress"`` with the IBM-reported
|
| 392 |
+
status and elapsed seconds. Final yield: ``status="completed"`` with
|
| 393 |
+
the full verification result, or ``status="timeout"`` if the live
|
| 394 |
+
poll loop exceeded :data:`LIVE_POLL_TIMEOUT_SECONDS`, or
|
| 395 |
+
``status="error"`` if something went wrong.
|
| 396 |
+
"""
|
| 397 |
if label not in EXAMPLES:
|
| 398 |
+
yield {"status": "error", "error": f"unknown example: {label}"}
|
| 399 |
+
return
|
| 400 |
try:
|
| 401 |
narrow_mode = _coerce_mode(mode)
|
| 402 |
except ValueError as exc:
|
| 403 |
+
yield {"status": "error", "error": str(exc)}
|
| 404 |
+
return
|
| 405 |
|
| 406 |
if not IBM_AVAILABLE:
|
| 407 |
+
yield {
|
| 408 |
"status": "error",
|
| 409 |
"error": (
|
| 410 |
"IBM_QUANTUM_TOKEN and/or IBM_QUANTUM_INSTANCE are not set "
|
|
|
|
| 412 |
"unavailable; use the simulator button."
|
| 413 |
),
|
| 414 |
}
|
| 415 |
+
return
|
| 416 |
|
| 417 |
start = time.monotonic()
|
| 418 |
try:
|
| 419 |
job, job_id, backend_name, prepared = _prepare_and_submit(label)
|
| 420 |
except Exception as exc:
|
| 421 |
+
yield {
|
| 422 |
"status": "error",
|
| 423 |
"error": f"{type(exc).__name__}: {exc}",
|
| 424 |
}
|
| 425 |
+
return
|
| 426 |
|
| 427 |
+
yield {
|
|
|
|
| 428 |
"status": "submitted",
|
| 429 |
"ibm_job_id": job_id,
|
| 430 |
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 431 |
"ibm_status": "QUEUED",
|
| 432 |
+
"seconds_elapsed": int(time.monotonic() - start),
|
| 433 |
"backend": backend_name,
|
| 434 |
"example": label,
|
| 435 |
"mode": narrow_mode,
|
| 436 |
"message": (
|
| 437 |
+
"Job submitted. Polling every "
|
| 438 |
+
f"{POLL_INTERVAL_SECONDS} s for up to "
|
| 439 |
+
f"{LIVE_POLL_TIMEOUT_SECONDS // 60} minutes. If your browser "
|
| 440 |
+
"loses the live stream before the job finishes, copy the "
|
| 441 |
+
"Job ID from this output and use the 'Recover a previous "
|
| 442 |
+
"job' panel below."
|
| 443 |
),
|
| 444 |
}
|
| 445 |
+
|
| 446 |
+
while True:
|
| 447 |
+
elapsed = int(time.monotonic() - start)
|
| 448 |
+
if elapsed > LIVE_POLL_TIMEOUT_SECONDS:
|
| 449 |
+
yield {
|
| 450 |
+
"status": "timeout",
|
| 451 |
+
"ibm_job_id": job_id,
|
| 452 |
+
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 453 |
+
"seconds_elapsed": elapsed,
|
| 454 |
+
"message": (
|
| 455 |
+
"Live wait exceeded "
|
| 456 |
+
f"{LIVE_POLL_TIMEOUT_SECONDS} s without the job "
|
| 457 |
+
"completing. The job is still running on IBM. Use "
|
| 458 |
+
"the 'Recover a previous job' panel below with this "
|
| 459 |
+
"Job ID once it finishes (typical wall-clock 2-20 "
|
| 460 |
+
"minutes for free-tier queue + run)."
|
| 461 |
+
),
|
| 462 |
+
}
|
| 463 |
+
return
|
| 464 |
+
|
| 465 |
+
time.sleep(POLL_INTERVAL_SECONDS)
|
| 466 |
+
|
| 467 |
+
try:
|
| 468 |
+
ibm_status = str(job.status())
|
| 469 |
+
except Exception as exc:
|
| 470 |
+
yield {
|
| 471 |
+
"status": "error",
|
| 472 |
+
"ibm_job_id": job_id,
|
| 473 |
+
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 474 |
+
"seconds_elapsed": int(time.monotonic() - start),
|
| 475 |
+
"error": f"polling failed: {type(exc).__name__}: {exc}",
|
| 476 |
+
}
|
| 477 |
+
return
|
| 478 |
+
|
| 479 |
+
if ibm_status not in IBM_TERMINAL_STATUSES:
|
| 480 |
+
yield {
|
| 481 |
+
"status": "in_progress",
|
| 482 |
+
"ibm_job_id": job_id,
|
| 483 |
+
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 484 |
+
"ibm_status": ibm_status,
|
| 485 |
+
"seconds_elapsed": int(time.monotonic() - start),
|
| 486 |
+
}
|
| 487 |
+
continue
|
| 488 |
+
|
| 489 |
+
if ibm_status != "DONE":
|
| 490 |
+
yield {
|
| 491 |
+
"status": "error",
|
| 492 |
+
"ibm_job_id": job_id,
|
| 493 |
+
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 494 |
+
"ibm_status": ibm_status,
|
| 495 |
+
"seconds_elapsed": int(time.monotonic() - start),
|
| 496 |
+
"error": f"IBM job ended with status {ibm_status}",
|
| 497 |
+
}
|
| 498 |
+
return
|
| 499 |
+
|
| 500 |
+
try:
|
| 501 |
+
result_obj = job.result()
|
| 502 |
+
pub_result = result_obj[0]
|
| 503 |
+
raw_counts = pub_result.data.meas.get_counts()
|
| 504 |
+
except Exception as exc:
|
| 505 |
+
yield {
|
| 506 |
+
"status": "error",
|
| 507 |
+
"ibm_job_id": job_id,
|
| 508 |
+
"ibm_job_url": IBM_CONSOLE_BASE + job_id,
|
| 509 |
+
"seconds_elapsed": int(time.monotonic() - start),
|
| 510 |
+
"error": f"result fetch failed: {type(exc).__name__}: {exc}",
|
| 511 |
+
}
|
| 512 |
+
return
|
| 513 |
+
|
| 514 |
+
counts = _decode_counts(raw_counts)
|
| 515 |
+
verification = _build_verification_result(counts, prepared, backend_name, narrow_mode)
|
| 516 |
+
wall = round(time.monotonic() - start, 1)
|
| 517 |
+
yield _final_payload(
|
| 518 |
+
label, narrow_mode, job_id, backend_name, verification, wall, ibm_status, int(wall)
|
| 519 |
+
)
|
| 520 |
+
return
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
# --------------------------------------------------------------------------
|
| 524 |
+
# Manual recovery path - look up a previous job by ID
|
| 525 |
+
# --------------------------------------------------------------------------
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def check_job_status(job_id: str) -> dict[str, Any]:
|
| 529 |
+
"""One-shot status query for a job already submitted (live stream lost)."""
|
| 530 |
+
job_id = (job_id or "").strip()
|
| 531 |
+
if not job_id:
|
| 532 |
+
return {"error": "Enter a Job ID first."}
|
| 533 |
+
|
| 534 |
if not IBM_AVAILABLE:
|
| 535 |
return {
|
| 536 |
"error": (
|
|
|
|
| 681 |
interactive=IBM_AVAILABLE,
|
| 682 |
)
|
| 683 |
gr.Markdown(_ibm_status_md)
|
|
|
|
| 684 |
|
| 685 |
with gr.Column(scale=1):
|
| 686 |
gr.Markdown("### Result")
|
| 687 |
output = gr.JSON(label="Verification result")
|
|
|
|
| 688 |
|
| 689 |
example_dd.change(
|
| 690 |
_on_example_change,
|
|
|
|
| 701 |
inputs=[example_dd, mode_radio],
|
| 702 |
outputs=output,
|
| 703 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
|
| 705 |
gr.Markdown("---")
|
| 706 |
gr.Markdown("### 4. Recover a previous job (fallback)")
|
|
|
|
| 734 |
"host the translator, so this Space is verifier-only. See the "
|
| 735 |
"[GitHub repository](https://github.com/Quantum-Labor/qverify) "
|
| 736 |
"for the end-to-end pipeline.\n\n"
|
| 737 |
+
"**IBM hardware runs may take 2-20 minutes** due to free-tier "
|
| 738 |
+
"queue plus on-device execution and result fetch. The live "
|
| 739 |
+
f"button polls automatically every {POLL_INTERVAL_SECONDS} s "
|
| 740 |
+
f"for up to {LIVE_POLL_TIMEOUT_SECONDS // 60} minutes; if the "
|
| 741 |
+
"connection drops during a long run, use the 'Recover a "
|
| 742 |
+
"previous job' panel above with the Job ID from your initial "
|
| 743 |
+
"submission. Hardware credit is shared across users on the "
|
| 744 |
+
"Open Plan (~10 minutes/month total)."
|
|
|
|
| 745 |
)
|
| 746 |
|
| 747 |
|