Laborator commited on
Commit
c685c2c
·
1 Parent(s): b3907b1

revert: rollback to working version with disclaimer

Browse files
Files changed (1) hide show
  1. app.py +123 -30
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 return job ID immediately (no polling)."""
 
 
 
 
 
 
 
 
389
  if label not in EXAMPLES:
390
- return {"status": "error", "error": f"unknown example: {label}"}
 
391
  try:
392
  narrow_mode = _coerce_mode(mode)
393
  except ValueError as exc:
394
- return {"status": "error", "error": str(exc)}
 
395
 
396
  if not IBM_AVAILABLE:
397
- return {
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
- return {
411
  "status": "error",
412
  "error": f"{type(exc).__name__}: {exc}",
413
  }
 
414
 
415
- elapsed = int(time.monotonic() - start)
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": elapsed,
422
  "backend": backend_name,
423
  "example": label,
424
  "mode": narrow_mode,
425
  "message": (
426
- f"Job submitted to {backend_name} in {elapsed}s. "
427
- "The job is now running on IBM Quantum hardware. "
428
- "Click 'Check job status' below to retrieve the result when ready "
429
- "(typically 1-3 minutes for queue + execution). "
430
- f"Job ID: {job_id}"
 
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 are submitted via your IBM Quantum "
644
- "free-tier account** (Open Plan, approximately 10 minutes of "
645
- "quantum time per month, shared across users). The Space "
646
- "orchestrates the call; the actual circuit executes on "
647
- "`ibm_fez` (Heron r2, 156 qubits) via IBM Cloud. Live polling "
648
- f"updates the UI every {POLL_INTERVAL_SECONDS} s; if the "
649
- "connection drops, use the fallback panel above with the Job "
650
- "ID. Previously verified hardware runs are documented "
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