harishaseebat92 commited on
Commit
dbdff89
·
1 Parent(s): 4f10fce

Add detailed progress tracking for IBM QPU

Browse files
em/simulation.py CHANGED
@@ -451,8 +451,8 @@ async def _run_simulation_async():
451
  if sve_selected:
452
  try:
453
  log_to_console("Running Statevector Estimator...")
454
- state.status_message = "Running Statevector Estimator simulation..."
455
- state.simulation_progress = 20
456
  await _flush_async()
457
 
458
  state.qpu_ts_ready = False
@@ -479,10 +479,24 @@ async def _run_simulation_async():
479
  p = getattr(state, f"qpu_monitor_gridpoints_{slot_num}", "") or ""
480
  configs.append({"field": f, "points": p})
481
 
482
- state.status_message = "Building Statevector Estimator time series..."
483
- state.simulation_progress = 60
484
  await _flush_async()
485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  def _sve_series_runner(field_type, positions, total_time, snapshot_dt, nx, impulse_pos, progress_callback=None, print_callback=None):
487
  return qutils.run_sve(
488
  field_type,
@@ -503,13 +517,18 @@ async def _run_simulation_async():
503
  return build_qpu_timeseries_plotly_multi(
504
  configs, nx, T, snapshot_dt, impulse_pos,
505
  series_runner=_sve_series_runner,
506
- progress_callback=_progress_callback,
507
  print_callback=log_to_console
508
  )
509
 
510
  fig = await loop.run_in_executor(executor, _run_sve_blocking)
511
  qpu_ts_cache["fig"] = fig
512
 
 
 
 
 
 
513
  try:
514
  ctrl.qpu_ts_update(fig)
515
  except Exception:
@@ -594,18 +613,31 @@ async def _run_simulation_async():
594
  if len(raw_pts) > 1:
595
  log_to_console(f"Warning: IBM QPU only supports single position. Using first: ({monitor_x}, {monitor_y})")
596
 
597
- state.status_message = "Step 1: Circuit Construction & Optimization (0-40%)..."
598
- state.simulation_progress = 10
599
  await _flush_async()
600
 
601
- def _ibm_progress_callback(pct):
 
 
 
 
 
 
 
602
  state.simulation_progress = int(pct)
603
- if pct < 40:
604
- state.status_message = f"Step 1: Circuit Construction & Optimization ({int(pct)}%)"
 
 
 
 
 
 
605
  elif pct < 90:
606
- state.status_message = f"Step 2: Circuit Execution ({int(pct)}%)"
607
  else:
608
- state.status_message = f"Step 3: Result Processing ({int(pct)}%)"
609
  _flush_state_threadsafe() # Thread-safe flush from callback thread
610
 
611
  # Call the IBM QPU get_field_values function in executor to keep UI responsive
 
451
  if sve_selected:
452
  try:
453
  log_to_console("Running Statevector Estimator...")
454
+ state.status_message = "Step 1: Initializing Statevector Estimator..."
455
+ state.simulation_progress = 5
456
  await _flush_async()
457
 
458
  state.qpu_ts_ready = False
 
479
  p = getattr(state, f"qpu_monitor_gridpoints_{slot_num}", "") or ""
480
  configs.append({"field": f, "points": p})
481
 
482
+ state.status_message = "Step 1: Setting up Statevector Estimator..."
483
+ state.simulation_progress = 10
484
  await _flush_async()
485
 
486
+ # SVE-specific progress callback that maps internal 0-100% to 10-90% range
487
+ # and shows appropriate step messages
488
+ def _sve_progress_callback(pct):
489
+ # Map internal progress (0-100%) to range 10-90%
490
+ mapped_pct = 10 + (pct * 0.8) # 10% to 90%
491
+ state.simulation_progress = int(mapped_pct)
492
+ if mapped_pct < 30:
493
+ state.status_message = f"Step 2: Building quantum circuits ({int(mapped_pct)}%)"
494
+ elif mapped_pct < 70:
495
+ state.status_message = f"Step 3: Running Statevector simulation ({int(mapped_pct)}%)"
496
+ else:
497
+ state.status_message = f"Step 4: Processing results ({int(mapped_pct)}%)"
498
+ _flush_state_threadsafe()
499
+
500
  def _sve_series_runner(field_type, positions, total_time, snapshot_dt, nx, impulse_pos, progress_callback=None, print_callback=None):
501
  return qutils.run_sve(
502
  field_type,
 
517
  return build_qpu_timeseries_plotly_multi(
518
  configs, nx, T, snapshot_dt, impulse_pos,
519
  series_runner=_sve_series_runner,
520
+ progress_callback=_sve_progress_callback,
521
  print_callback=log_to_console
522
  )
523
 
524
  fig = await loop.run_in_executor(executor, _run_sve_blocking)
525
  qpu_ts_cache["fig"] = fig
526
 
527
+ # Step 5: Creating plots (90-100%)
528
+ state.simulation_progress = 95
529
+ state.status_message = "Step 5: Creating plots (95%)"
530
+ _flush_state()
531
+
532
  try:
533
  ctrl.qpu_ts_update(fig)
534
  except Exception:
 
613
  if len(raw_pts) > 1:
614
  log_to_console(f"Warning: IBM QPU only supports single position. Using first: ({monitor_x}, {monitor_y})")
615
 
616
+ state.status_message = "Step 1: Generating circuit..."
617
+ state.simulation_progress = 0
618
  await _flush_async()
619
 
620
+ def _ibm_progress_callback(pct, message=None):
621
+ """
622
+ Progress callback for IBM QPU with 4-step pattern:
623
+ Step 1: Generating circuit (0-10%)
624
+ Step 2: Optimising Circuit (10-60%)
625
+ Step 3: Job Submitted + Status monitoring (60-90%)
626
+ Step 4: Creating Plots (90-100%)
627
+ """
628
  state.simulation_progress = int(pct)
629
+
630
+ if message:
631
+ state.status_message = message
632
+ elif pct < 10:
633
+ state.status_message = f"Step 1: Generating circuit ({int(pct)}%)"
634
+ elif pct < 60:
635
+ # Map 10-40% internal to 10-60% display
636
+ state.status_message = f"Step 2: Optimising circuit ({int(pct)}%)"
637
  elif pct < 90:
638
+ state.status_message = f"Step 3: Job execution ({int(pct)}%)"
639
  else:
640
+ state.status_message = f"Step 4: Creating plots ({int(pct)}%)"
641
  _flush_state_threadsafe() # Thread-safe flush from callback thread
642
 
643
  # Call the IBM QPU get_field_values function in executor to keep UI responsive
qlbm/qlbm_sample_app.py CHANGED
@@ -513,7 +513,8 @@ def run_sampling_hw_ibm(
513
  vel_resolution=32,
514
  output_resolution=40,
515
  logger=None,
516
- flag_qubits=True
 
517
  ):
518
  """
519
  Run QLBM simulation on IBM quantum hardware.
@@ -536,6 +537,8 @@ def run_sampling_hw_ibm(
536
  Grid resolution for density estimation output
537
  logger : callable, optional
538
  Function to log messages (e.g. print to console)
 
 
539
 
540
  Returns
541
  -------
@@ -544,6 +547,7 @@ def run_sampling_hw_ibm(
544
  get_job_result : callable
545
  Callback function to retrieve and process results. Returns (output, fig).
546
  """
 
547
 
548
  def log(msg):
549
  if logger:
@@ -551,62 +555,144 @@ def run_sampling_hw_ibm(
551
  else:
552
  print(msg)
553
 
554
- # if type(ux)==str:
555
- # ux,uy,uz=str_to_lambda(ux,uy,uz)
 
556
 
557
- # # Convert string init_state_prep_circ to circuit if needed (matches original logic)
558
- # if type(init_state_prep_circ)==str:
559
- # init_state_prep_circ=get_named_init_state_circuit(n,init_state_prep_circ)
560
 
561
  qc_list=get_circuit(n,ux,uy,uz,init_state_prep_circ,T_list,vel_resolution,flag_qubits=flag_qubits)
 
 
 
562
 
563
  pm_optimization_level = 3
564
 
565
- service = QiskitRuntimeService(channel="ibm_cloud", token="UMeZUDI5D7fjPJHD5x3MJFwURg4PrGzBnTm142ka9-Hj",instance="crn:v1:bluemix:public:quantum-computing:us-east:a/15157e4350c04a9dab51b8b8a4a93c86:e29afd91-64bf-4a82-8dbf-731e6c213595::") # reads stored credentials / environment
 
 
 
566
  backend = service.least_busy()
 
 
567
 
568
  qc_compiled_list=[]
 
569
 
570
- for qc in qc_list:
 
 
 
571
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
572
- log("Generating ISA circuit via PassManager (preserves measurements/conditionals).")
573
- qc_compiled = pm.run(qc) # this is the recommended replacement for transpile(..., backend=backend)
574
- log(f"Compiled circuit qubits/clbits: {qc_compiled.num_qubits} {qc_compiled.num_clbits}")
575
- log(f"Depth: {qc_compiled.depth()}")
576
  qc_compiled_list+=[qc_compiled]
577
 
578
- # Create Sampler primitive bound to the backend
579
- sampler = Sampler(mode=backend)
580
 
581
- # # Submit job: pass a list of PUBs (we send one PUB [qc_compiled])
 
582
  job = sampler.run(qc_compiled_list, shots=shots)
583
- log("Job submitted; waiting for result...")
 
 
584
 
585
  def get_job_result(j):
586
- result = j.result() # PrimitiveResult (a container of PubResults)
587
- log("Waiting for job results (this may take time)...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
 
589
  output=[]
 
590
 
591
- for T_total,pub in zip(T_list,result):
 
 
592
 
593
- # Try join_data() to combine multiple regs and get_counts() on it
594
  try:
595
- joined = pub.join_data() # join_data concatenates registers along bits axis
596
  joined_counts = joined.get_counts()
597
  except Exception as e:
598
- log(f"Error retrieving counts: {e}")
599
  joined_counts = None
600
 
601
- # Suppress verbose logging by passing None as logger
602
  pts, counts = load_samples(joined_counts, T_total, logger=None, flag_qubits=flag_qubits)
603
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
604
 
605
  log(f"Processing complete: {len(output)} timestep(s)")
 
 
606
  fig = plot_density_isosurface_slider(output, T_list)
 
 
 
 
607
  return output, fig
608
 
609
- return job,get_job_result
610
 
611
  from qiskit_ionq import IonQProvider
612
 
@@ -623,7 +709,8 @@ def run_sampling_hw_ionq(
623
  vel_resolution=32,
624
  output_resolution=40,
625
  logger=None,
626
- flag_qubits=True
 
627
  ):
628
  """
629
  Run QLBM simulation on IonQ quantum hardware.
@@ -646,6 +733,8 @@ def run_sampling_hw_ionq(
646
  Grid resolution for density estimation output
647
  logger : callable, optional
648
  Function to log messages (e.g. print to console)
 
 
649
 
650
  Returns
651
  -------
@@ -654,6 +743,7 @@ def run_sampling_hw_ionq(
654
  get_job_result : callable
655
  Callback function to retrieve and process results. Returns (output, fig).
656
  """
 
657
 
658
  def log(msg):
659
  if logger:
@@ -661,49 +751,110 @@ def run_sampling_hw_ionq(
661
  else:
662
  print(msg)
663
 
664
- # if type(ux)==str:
665
- # ux,uy,uz=str_to_lambda(ux,uy,uz)
 
666
 
667
- # # Convert string init_state_prep_circ to circuit if needed (matches original logic)
668
- # if type(init_state_prep_circ)==str:
669
- # init_state_prep_circ=get_named_init_state_circuit(n,init_state_prep_circ)
670
 
671
  # backend = provider.get_backend("simulator")
672
  backend = provider.get_backend("qpu.forte-enterprise-1")
 
 
673
 
674
  qc_list=get_circuit(n,ux,uy,uz,init_state_prep_circ,T_list,vel_resolution,flag_qubits=flag_qubits,midcircuit_meas=False)
 
 
 
675
 
676
- # Create Sampler primitive bound to the backend
 
 
677
 
678
  job = backend.run(qc_list, shots=shots)
679
-
680
- # job = backend.retrieve_job("019b0aec-36d7-749a-89f2-c36382b3aa1c")
681
-
682
- # sampler = Sampler(mode=backend)
683
-
684
- # # Submit job: pass a list of PUBs (we send one PUB [qc_compiled])
685
- # job = sampler.run(qc_compiled_list, shots=shots)
686
- log("Job submitted; waiting for result...")
687
 
688
  def get_job_result(j):
 
 
 
 
 
 
 
 
689
 
690
- log("Waiting for job results (this may take time)...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
 
 
 
692
  output=[]
 
693
 
694
- for i,T_total in enumerate(T_list):
 
 
695
 
696
  counts = j.get_counts(i)
697
-
698
- # Suppress verbose logging by passing None as logger
699
  pts, counts = load_samples(counts, T_total, logger=None, flag_qubits=flag_qubits, midcircuit_meas=False)
700
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
701
 
702
  log(f"Processing complete: {len(output)} timestep(s)")
 
 
703
  fig = plot_density_isosurface_slider(output, T_list)
 
 
 
 
704
  return output, fig
705
 
706
- return job,get_job_result
707
 
708
 
709
 
 
513
  vel_resolution=32,
514
  output_resolution=40,
515
  logger=None,
516
+ flag_qubits=True,
517
+ progress_callback=None,
518
  ):
519
  """
520
  Run QLBM simulation on IBM quantum hardware.
 
537
  Grid resolution for density estimation output
538
  logger : callable, optional
539
  Function to log messages (e.g. print to console)
540
+ progress_callback : callable, optional
541
+ Function to report progress (0-100) with optional status message: progress_callback(percent, message)
542
 
543
  Returns
544
  -------
 
547
  get_job_result : callable
548
  Callback function to retrieve and process results. Returns (output, fig).
549
  """
550
+ import time as time_module
551
 
552
  def log(msg):
553
  if logger:
 
555
  else:
556
  print(msg)
557
 
558
+ def update_progress(percent, message=None):
559
+ if progress_callback:
560
+ progress_callback(percent, message)
561
 
562
+ # === STEP 1: Circuit Generation (0-50%) ===
563
+ log("Step 1: Generating quantum circuits...")
564
+ update_progress(5, "Generating quantum circuits...")
565
 
566
  qc_list=get_circuit(n,ux,uy,uz,init_state_prep_circ,T_list,vel_resolution,flag_qubits=flag_qubits)
567
+
568
+ log(f"Generated {len(qc_list)} circuit(s) for timesteps {T_list}")
569
+ update_progress(15, f"Generated {len(qc_list)} circuits")
570
 
571
  pm_optimization_level = 3
572
 
573
+ log("Connecting to IBM Quantum service...")
574
+ update_progress(20, "Connecting to IBM Quantum...")
575
+
576
+ service = QiskitRuntimeService(channel="ibm_cloud", token="UMeZUDI5D7fjPJHD5x3MJFwURg4PrGzBnTm142ka9-Hj",instance="crn:v1:bluemix:public:quantum-computing:us-east:a/15157e4350c04a9dab51b8b8a4a93c86:e29afd91-64bf-4a82-8dbf-731e6c213595::")
577
  backend = service.least_busy()
578
+ log(f"Selected backend: {backend.name}")
579
+ update_progress(25, f"Backend: {backend.name}")
580
 
581
  qc_compiled_list=[]
582
+ total_circuits = len(qc_list)
583
 
584
+ for idx, qc in enumerate(qc_list):
585
+ circuit_progress = 25 + (idx / total_circuits) * 20 # 25-45%
586
+ update_progress(circuit_progress, f"Transpiling circuit {idx+1}/{total_circuits}...")
587
+
588
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
589
+ log(f"Transpiling circuit {idx+1}/{total_circuits} via PassManager...")
590
+ qc_compiled = pm.run(qc)
591
+ log(f" Compiled: {qc_compiled.num_qubits} qubits, {qc_compiled.num_clbits} clbits, depth={qc_compiled.depth()}")
 
592
  qc_compiled_list+=[qc_compiled]
593
 
594
+ log("All circuits transpiled successfully.")
595
+ update_progress(50, "Circuits ready. Submitting job...")
596
 
597
+ # === STEP 2: Job Submission & Monitoring (50-90%) ===
598
+ sampler = Sampler(mode=backend)
599
  job = sampler.run(qc_compiled_list, shots=shots)
600
+ job_id = job.job_id() if hasattr(job, 'job_id') else str(job)
601
+ log(f"Job submitted! Job ID: {job_id}")
602
+ update_progress(52, f"Job submitted: {job_id}")
603
 
604
  def get_job_result(j):
605
+ """Poll job status and retrieve results with progress updates."""
606
+ # Job status polling with progress updates
607
+ log("Monitoring job status...")
608
+ update_progress(55, "Job queued, waiting for execution...")
609
+
610
+ # Status mapping for progress estimation
611
+ # JobStatus: INITIALIZING, QUEUED, VALIDATING, RUNNING, CANCELLED, DONE, ERROR
612
+ status_progress_map = {
613
+ 'INITIALIZING': (55, "Job initializing..."),
614
+ 'QUEUED': (58, "Job queued, waiting..."),
615
+ 'VALIDATING': (62, "Validating job..."),
616
+ 'RUNNING': (70, "Job running on QPU..."),
617
+ 'DONE': (85, "Job completed!"),
618
+ 'ERROR': (85, "Job error occurred"),
619
+ 'CANCELLED': (85, "Job cancelled"),
620
+ }
621
+
622
+ last_status = None
623
+ poll_count = 0
624
+ max_polls = 600 # ~10 minutes with 1s interval
625
+
626
+ while poll_count < max_polls:
627
+ try:
628
+ status = j.status()
629
+ status_name = status.name if hasattr(status, 'name') else str(status)
630
+
631
+ if status_name != last_status:
632
+ last_status = status_name
633
+ progress_val, status_msg = status_progress_map.get(status_name, (60, f"Status: {status_name}"))
634
+ log(f"Job Status: {status_name}")
635
+ update_progress(progress_val, status_msg)
636
+
637
+ # Check if job is complete
638
+ if status_name in ('DONE', 'ERROR', 'CANCELLED'):
639
+ break
640
+
641
+ # Increment progress slightly while waiting (indeterminate feel)
642
+ if status_name == 'QUEUED':
643
+ # Slowly increment between 58-65 while queued
644
+ queue_progress = 58 + min(7, poll_count * 0.05)
645
+ update_progress(queue_progress, f"Queued... (waiting {poll_count}s)")
646
+ elif status_name == 'RUNNING':
647
+ # Slowly increment between 70-85 while running
648
+ run_progress = 70 + min(15, poll_count * 0.1)
649
+ update_progress(run_progress, f"Running on QPU... ({poll_count}s)")
650
+
651
+ time_module.sleep(1)
652
+ poll_count += 1
653
+
654
+ except Exception as e:
655
+ log(f"Status check error: {e}")
656
+ time_module.sleep(2)
657
+ poll_count += 2
658
+
659
+ # Get results
660
+ log("Retrieving job results...")
661
+ update_progress(87, "Retrieving results...")
662
+
663
+ result = j.result()
664
+ log("Results retrieved successfully.")
665
+ update_progress(90, "Processing results...")
666
 
667
+ # === STEP 3: Creating Plots (90-100%) ===
668
  output=[]
669
+ total_timesteps = len(T_list)
670
 
671
+ for idx, (T_total, pub) in enumerate(zip(T_list, result)):
672
+ plot_progress = 90 + (idx / total_timesteps) * 8 # 90-98%
673
+ update_progress(plot_progress, f"Processing timestep T={T_total}...")
674
 
 
675
  try:
676
+ joined = pub.join_data()
677
  joined_counts = joined.get_counts()
678
  except Exception as e:
679
+ log(f"Error retrieving counts for T={T_total}: {e}")
680
  joined_counts = None
681
 
 
682
  pts, counts = load_samples(joined_counts, T_total, logger=None, flag_qubits=flag_qubits)
683
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
684
 
685
  log(f"Processing complete: {len(output)} timestep(s)")
686
+ update_progress(98, "Creating visualization...")
687
+
688
  fig = plot_density_isosurface_slider(output, T_list)
689
+
690
+ update_progress(100, "Complete!")
691
+ log("IBM QPU job completed successfully.")
692
+
693
  return output, fig
694
 
695
+ return job, get_job_result
696
 
697
  from qiskit_ionq import IonQProvider
698
 
 
709
  vel_resolution=32,
710
  output_resolution=40,
711
  logger=None,
712
+ flag_qubits=True,
713
+ progress_callback=None,
714
  ):
715
  """
716
  Run QLBM simulation on IonQ quantum hardware.
 
733
  Grid resolution for density estimation output
734
  logger : callable, optional
735
  Function to log messages (e.g. print to console)
736
+ progress_callback : callable, optional
737
+ Function to report progress (0-100) with optional status message: progress_callback(percent, message)
738
 
739
  Returns
740
  -------
 
743
  get_job_result : callable
744
  Callback function to retrieve and process results. Returns (output, fig).
745
  """
746
+ import time as time_module
747
 
748
  def log(msg):
749
  if logger:
 
751
  else:
752
  print(msg)
753
 
754
+ def update_progress(percent, message=None):
755
+ if progress_callback:
756
+ progress_callback(percent, message)
757
 
758
+ # === STEP 1: Circuit Generation (0-50%) ===
759
+ log("Step 1: Generating quantum circuits...")
760
+ update_progress(5, "Generating quantum circuits...")
761
 
762
  # backend = provider.get_backend("simulator")
763
  backend = provider.get_backend("qpu.forte-enterprise-1")
764
+ log(f"Selected IonQ backend: {backend.name()}")
765
+ update_progress(15, f"Backend: {backend.name()}")
766
 
767
  qc_list=get_circuit(n,ux,uy,uz,init_state_prep_circ,T_list,vel_resolution,flag_qubits=flag_qubits,midcircuit_meas=False)
768
+
769
+ log(f"Generated {len(qc_list)} circuit(s) for timesteps {T_list}")
770
+ update_progress(45, f"Generated {len(qc_list)} circuits")
771
 
772
+ # === STEP 2: Job Submission (50%) ===
773
+ log("Submitting job to IonQ...")
774
+ update_progress(50, "Submitting job to IonQ...")
775
 
776
  job = backend.run(qc_list, shots=shots)
777
+ job_id = job.job_id() if hasattr(job, 'job_id') else str(job)
778
+ log(f"Job submitted! Job ID: {job_id}")
779
+ update_progress(52, f"Job submitted: {job_id}")
 
 
 
 
 
780
 
781
  def get_job_result(j):
782
+ """Poll job status and retrieve results with progress updates."""
783
+ log("Monitoring IonQ job status...")
784
+ update_progress(55, "Job queued, waiting for execution...")
785
+
786
+ # IonQ job status polling
787
+ last_status = None
788
+ poll_count = 0
789
+ max_polls = 600 # ~10 minutes with 1s interval
790
 
791
+ while poll_count < max_polls:
792
+ try:
793
+ status = j.status()
794
+ status_name = status.name if hasattr(status, 'name') else str(status)
795
+
796
+ if status_name != last_status:
797
+ last_status = status_name
798
+ log(f"Job Status: {status_name}")
799
+
800
+ if status_name in ('QUEUED', 'VALIDATING'):
801
+ update_progress(58, f"Status: {status_name}")
802
+ elif status_name == 'RUNNING':
803
+ update_progress(70, "Job running on IonQ QPU...")
804
+ elif status_name == 'DONE':
805
+ update_progress(85, "Job completed!")
806
+ break
807
+ elif status_name in ('ERROR', 'CANCELLED'):
808
+ update_progress(85, f"Job {status_name.lower()}")
809
+ break
810
+
811
+ # Increment progress slightly while waiting
812
+ if status_name == 'QUEUED':
813
+ queue_progress = 58 + min(7, poll_count * 0.05)
814
+ update_progress(queue_progress, f"Queued... (waiting {poll_count}s)")
815
+ elif status_name == 'RUNNING':
816
+ run_progress = 70 + min(15, poll_count * 0.1)
817
+ update_progress(run_progress, f"Running on IonQ... ({poll_count}s)")
818
+
819
+ # Check if done
820
+ if status_name in ('DONE', 'ERROR', 'CANCELLED'):
821
+ break
822
+
823
+ time_module.sleep(1)
824
+ poll_count += 1
825
+
826
+ except Exception as e:
827
+ log(f"Status check error: {e}")
828
+ time_module.sleep(2)
829
+ poll_count += 2
830
+
831
+ log("Retrieving IonQ job results...")
832
+ update_progress(87, "Retrieving results...")
833
 
834
+ # === STEP 3: Creating Plots (90-100%) ===
835
+ update_progress(90, "Processing results...")
836
  output=[]
837
+ total_timesteps = len(T_list)
838
 
839
+ for i, T_total in enumerate(T_list):
840
+ plot_progress = 90 + (i / total_timesteps) * 8 # 90-98%
841
+ update_progress(plot_progress, f"Processing timestep T={T_total}...")
842
 
843
  counts = j.get_counts(i)
 
 
844
  pts, counts = load_samples(counts, T_total, logger=None, flag_qubits=flag_qubits, midcircuit_meas=False)
845
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
846
 
847
  log(f"Processing complete: {len(output)} timestep(s)")
848
+ update_progress(98, "Creating visualization...")
849
+
850
  fig = plot_density_isosurface_slider(output, T_list)
851
+
852
+ update_progress(100, "Complete!")
853
+ log("IonQ job completed successfully.")
854
+
855
  return output, fig
856
 
857
+ return job, get_job_result
858
 
859
 
860
 
qlbm_embedded.py CHANGED
@@ -1465,6 +1465,13 @@ async def _run_simulation_async():
1465
  last_logged_percent[0] = percent
1466
  _qlbm_flush_state_threadsafe() # Thread-safe flush!
1467
 
 
 
 
 
 
 
 
1468
  try:
1469
  # === Qiskit Backend (IBM Qiskit Simulator) ===
1470
  if use_qiskit:
@@ -1501,16 +1508,17 @@ async def _run_simulation_async():
1501
  # === IBM QPU Backend ===
1502
  elif use_ibm_qpu:
1503
  log_to_console("Using IBM QPU backend...")
1504
- _state.qlbm_status_message = "Preparing IBM QPU job..."
 
1505
  await _qlbm_flush_async()
1506
 
1507
  params = _map_state_to_qiskit_params()
1508
  if params is None:
1509
  raise RuntimeError("Failed to map state parameters")
1510
 
1511
- # Create initial state circuit
1512
  log_to_console("Creating initial state circuit...")
1513
- _state.qlbm_simulation_progress = 10
1514
  await _qlbm_flush_async()
1515
 
1516
  init_state_prep_circ = get_named_init_state_circuit(
@@ -1528,14 +1536,12 @@ async def _run_simulation_async():
1528
  mdd_kz_log2=params["mdd_kz_log2"],
1529
  )
1530
 
1531
- log_to_console("Submitting job to IBM Quantum...")
1532
- _state.qlbm_status_message = "Submitting job to IBM Quantum..."
1533
- _state.qlbm_simulation_progress = 20
1534
  await _qlbm_flush_async()
1535
 
1536
- # Run HW simulation in executor
1537
  def _run_ibm_qpu_blocking():
1538
- log_to_console("Submitting and running IBM QPU job...")
1539
  job, get_result = run_sampling_hw_ibm(
1540
  n=params["n"],
1541
  ux=params["vx_expr"],
@@ -1546,19 +1552,18 @@ async def _run_simulation_async():
1546
  shots=2**14,
1547
  vel_resolution=min(params['grid_size'], 32),
1548
  output_resolution=min(2*params['grid_size'], 40),
1549
- logger=log_to_console
 
1550
  )
1551
- log_to_console("Waiting for job results (this may take time)...")
1552
  output, plotly_fig = get_result(job)
1553
  return output, plotly_fig, init_state_prep_circ
1554
 
1555
- _state.qlbm_status_message = "Waiting for IBM QPU results..."
1556
- _state.qlbm_simulation_progress = 40
1557
- await _qlbm_flush_async()
1558
-
1559
  output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ibm_qpu_blocking)
1560
 
1561
- _state.qlbm_simulation_progress = 80
 
 
1562
  await _qlbm_flush_async()
1563
 
1564
  # Generate T=0 initial snapshot and prepend to output
@@ -1618,16 +1623,17 @@ async def _run_simulation_async():
1618
  # === IonQ QPU Backend ===
1619
  elif use_ionq_qpu:
1620
  log_to_console("Using IonQ QPU backend...")
1621
- _state.qlbm_status_message = "Preparing IonQ QPU job..."
 
1622
  await _qlbm_flush_async()
1623
 
1624
  params = _map_state_to_qiskit_params()
1625
  if params is None:
1626
  raise RuntimeError("Failed to map state parameters")
1627
 
1628
- # Create initial state circuit
1629
  log_to_console("Creating initial state circuit...")
1630
- _state.qlbm_simulation_progress = 10
1631
  await _qlbm_flush_async()
1632
 
1633
  init_state_prep_circ = get_named_init_state_circuit(
@@ -1645,14 +1651,12 @@ async def _run_simulation_async():
1645
  mdd_kz_log2=params["mdd_kz_log2"],
1646
  )
1647
 
1648
- log_to_console("Submitting job to IonQ Quantum...")
1649
- _state.qlbm_status_message = "Submitting job to IonQ Quantum..."
1650
- _state.qlbm_simulation_progress = 20
1651
  await _qlbm_flush_async()
1652
 
1653
- # Run IonQ HW simulation in executor
1654
  def _run_ionq_qpu_blocking():
1655
- log_to_console("Submitting and running IonQ QPU job...")
1656
  job, get_result = run_sampling_hw_ionq(
1657
  n=params["n"],
1658
  ux=params["vx_expr"],
@@ -1663,19 +1667,18 @@ async def _run_simulation_async():
1663
  shots=2**14,
1664
  vel_resolution=min(params['grid_size'], 32),
1665
  output_resolution=min(2*params['grid_size'], 40),
1666
- logger=log_to_console
 
1667
  )
1668
- log_to_console("Waiting for IonQ job results (this may take time)...")
1669
  output, plotly_fig = get_result(job)
1670
  return output, plotly_fig, init_state_prep_circ
1671
 
1672
- _state.qlbm_status_message = "Waiting for IonQ QPU results..."
1673
- _state.qlbm_simulation_progress = 40
1674
- await _qlbm_flush_async()
1675
-
1676
  output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ionq_qpu_blocking)
1677
 
1678
- _state.qlbm_simulation_progress = 80
 
 
1679
  await _qlbm_flush_async()
1680
 
1681
  # Generate T=0 initial snapshot and prepend to output
 
1465
  last_logged_percent[0] = percent
1466
  _qlbm_flush_state_threadsafe() # Thread-safe flush!
1467
 
1468
+ # QPU progress callback with status message support
1469
+ def _qpu_progress_callback(percent, message=None):
1470
+ _state.qlbm_simulation_progress = percent
1471
+ if message:
1472
+ _state.qlbm_status_message = message
1473
+ _qlbm_flush_state_threadsafe()
1474
+
1475
  try:
1476
  # === Qiskit Backend (IBM Qiskit Simulator) ===
1477
  if use_qiskit:
 
1508
  # === IBM QPU Backend ===
1509
  elif use_ibm_qpu:
1510
  log_to_console("Using IBM QPU backend...")
1511
+ _state.qlbm_status_message = "Step 1: Preparing IBM QPU job..."
1512
+ _state.qlbm_simulation_progress = 0
1513
  await _qlbm_flush_async()
1514
 
1515
  params = _map_state_to_qiskit_params()
1516
  if params is None:
1517
  raise RuntimeError("Failed to map state parameters")
1518
 
1519
+ # Create initial state circuit (part of Step 1)
1520
  log_to_console("Creating initial state circuit...")
1521
+ _state.qlbm_simulation_progress = 2
1522
  await _qlbm_flush_async()
1523
 
1524
  init_state_prep_circ = get_named_init_state_circuit(
 
1536
  mdd_kz_log2=params["mdd_kz_log2"],
1537
  )
1538
 
1539
+ _state.qlbm_simulation_progress = 5
1540
+ _state.qlbm_status_message = "Step 1: Circuit generation..."
 
1541
  await _qlbm_flush_async()
1542
 
1543
+ # Run HW simulation in executor with progress callback
1544
  def _run_ibm_qpu_blocking():
 
1545
  job, get_result = run_sampling_hw_ibm(
1546
  n=params["n"],
1547
  ux=params["vx_expr"],
 
1552
  shots=2**14,
1553
  vel_resolution=min(params['grid_size'], 32),
1554
  output_resolution=min(2*params['grid_size'], 40),
1555
+ logger=log_to_console,
1556
+ progress_callback=_qpu_progress_callback,
1557
  )
1558
+ # get_result already handles progress updates internally
1559
  output, plotly_fig = get_result(job)
1560
  return output, plotly_fig, init_state_prep_circ
1561
 
 
 
 
 
1562
  output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ibm_qpu_blocking)
1563
 
1564
+ # Step 3: Creating Plots is already handled in get_job_result, now add T=0 snapshot
1565
+ _state.qlbm_simulation_progress = 92
1566
+ _state.qlbm_status_message = "Step 3: Adding T=0 snapshot..."
1567
  await _qlbm_flush_async()
1568
 
1569
  # Generate T=0 initial snapshot and prepend to output
 
1623
  # === IonQ QPU Backend ===
1624
  elif use_ionq_qpu:
1625
  log_to_console("Using IonQ QPU backend...")
1626
+ _state.qlbm_status_message = "Step 1: Preparing IonQ QPU job..."
1627
+ _state.qlbm_simulation_progress = 0
1628
  await _qlbm_flush_async()
1629
 
1630
  params = _map_state_to_qiskit_params()
1631
  if params is None:
1632
  raise RuntimeError("Failed to map state parameters")
1633
 
1634
+ # Create initial state circuit (part of Step 1)
1635
  log_to_console("Creating initial state circuit...")
1636
+ _state.qlbm_simulation_progress = 2
1637
  await _qlbm_flush_async()
1638
 
1639
  init_state_prep_circ = get_named_init_state_circuit(
 
1651
  mdd_kz_log2=params["mdd_kz_log2"],
1652
  )
1653
 
1654
+ _state.qlbm_simulation_progress = 5
1655
+ _state.qlbm_status_message = "Step 1: Circuit generation..."
 
1656
  await _qlbm_flush_async()
1657
 
1658
+ # Run IonQ HW simulation in executor with progress callback
1659
  def _run_ionq_qpu_blocking():
 
1660
  job, get_result = run_sampling_hw_ionq(
1661
  n=params["n"],
1662
  ux=params["vx_expr"],
 
1667
  shots=2**14,
1668
  vel_resolution=min(params['grid_size'], 32),
1669
  output_resolution=min(2*params['grid_size'], 40),
1670
+ logger=log_to_console,
1671
+ progress_callback=_qpu_progress_callback,
1672
  )
1673
+ # get_result already handles progress updates internally
1674
  output, plotly_fig = get_result(job)
1675
  return output, plotly_fig, init_state_prep_circ
1676
 
 
 
 
 
1677
  output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ionq_qpu_blocking)
1678
 
1679
+ # Step 3: Creating Plots is already handled in get_job_result, now add T=0 snapshot
1680
+ _state.qlbm_simulation_progress = 92
1681
+ _state.qlbm_status_message = "Step 3: Adding T=0 snapshot..."
1682
  await _qlbm_flush_async()
1683
 
1684
  # Generate T=0 initial snapshot and prepend to output
utils/EBU_Quantum/no_body/base_functions.py CHANGED
@@ -277,25 +277,52 @@ def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yr
277
 
278
  return qc, qcdiff
279
 
280
- def get_absolute_field_values(all_circuits, shots, pm_optimization_level, simulation = "True", platform = "IBM"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  jobs = {}
282
  job_status = {key: "QUEUED" for key in jobs}
283
- # while pending:
284
- # for key in pending[:]:
285
- # status = jobs[key].status()
286
- # job_status[key] = status
287
- # if status == "DONE":
288
- # pending.remove(key)
289
- # time.sleep(2)
290
 
291
  if simulation=="True":
292
  backend = Aer.get_backend('qasm_simulator')
 
 
 
293
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
294
  with Batch(backend=backend, max_time="2h") as batch:
295
  estimator = Estimator()
296
- for key, qc in all_circuits.items():
297
- # Decompose high-level gates to avoid Rust panic
298
- # qc = qc.decompose(["state_preparation", "initialize", "unitary", "isometry"])
299
  qc = transpile(qc, basis_gates=['u', 'cx', 'rz', 'sx', 'x'])
300
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
301
  qc_compiled = pm.run(qc)
@@ -303,11 +330,22 @@ def get_absolute_field_values(all_circuits, shots, pm_optimization_level, simula
303
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
304
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
305
  jobs[key] = job
 
 
 
 
 
306
  elif simulation == "False" and platform == "IBM":
 
 
 
307
  service = QiskitRuntimeService(channel="ibm_cloud",
308
  token="3Upysb5eOsRuBFlML0KV0uO4Jv4Lq01MhYsJ_Nu1pszy",
309
  instance="crn:v1:bluemix:public:quantum-computing:us-east:a/15157e4350c04a9dab51b8b8a4a93c86:e29afd91-64bf-4a82-8dbf-731e6c213595::")
310
  backend = service.least_busy(operational=True, simulator=False)
 
 
 
311
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
312
  with Batch(backend=backend, max_time="2h") as batch:
313
  estimator = Estimator()
@@ -318,9 +356,8 @@ def get_absolute_field_values(all_circuits, shots, pm_optimization_level, simula
318
  estimator.options.resilience.zne_mitigation = True
319
  estimator.options.resilience.zne.noise_factors = (1.1, 1.3, 1.5)
320
  estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
321
- for key, qc in all_circuits.items():
322
- # Decompose high-level gates to avoid Rust panic
323
- # qc = qc.decompose(["state_preparation", "initialize", "unitary", "isometry"])
324
  qc = transpile(qc, basis_gates=['u', 'cx', 'rz', 'sx', 'x'])
325
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
326
  qc_compiled = pm.run(qc)
@@ -328,30 +365,98 @@ def get_absolute_field_values(all_circuits, shots, pm_optimization_level, simula
328
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
329
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
330
  jobs[key] = job
 
 
 
 
 
331
  elif simulation == "False" and platform == "IONQ":
332
- # provider = IonQProvider(token='SgUkiDq1r2bVEadyiUfvtuxQ03Qci6UW')
333
- # ionq_backend = provider.get_backend("simulator")
334
- # ionq_backend.set_options(noise_model="ideal")
335
 
336
  provider = IonQProvider(token = 'UzmBNbWXGKjyKrGyvwiw6E3rYhPdL8AU')
337
  ionq_backend = provider.get_backend("qpu.forte-enterprise-1")
 
 
338
 
339
  pm = generate_preset_pass_manager(backend=ionq_backend, optimization_level=1)
340
  with Batch(backend=ionq_backend, max_time="2h") as batch:
341
  estimator = Estimator()
342
- for key, qc in all_circuits.items():
343
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
344
- qc_compiled = transpile(qc,basis_gates=['ry','rz','rx','h','cx'])# pm.run(qc)
345
  layout = qc_compiled.layout
346
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
347
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
348
  jobs[key] = job
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  #########################################################################################################
350
  results = {}
351
- for key, job in jobs.items():
352
  res = job.result()[0]
353
  z_exp = res.data.evs.item() # expectation value
354
  results[key] = np.sqrt((1 - z_exp) / 2)
 
 
 
 
 
 
355
  #########################################################################################
356
  return results
357
 
@@ -590,7 +695,7 @@ def get_field_values(field, x, y, T, snapshot_time, nx, impulse_pos, shots, pm_o
590
  except Exception:
591
  pass
592
 
593
- results = get_absolute_field_values(all_circuits, shots, pm_optimization_level, simulation, platform)
594
 
595
  # --- Step 3: Result Processing (90-100%) ---
596
  _log("Step 3: Result Processing")
 
277
 
278
  return qc, qcdiff
279
 
280
+ def get_absolute_field_values(all_circuits, shots, pm_optimization_level, simulation = "True", platform = "IBM", progress_callback=None, print_callback=None):
281
+ """
282
+ Execute circuits on quantum backend and return field values.
283
+
284
+ Parameters
285
+ ----------
286
+ progress_callback : callable, optional
287
+ Function to report progress (40-90 range for Step 2 execution)
288
+ print_callback : callable, optional
289
+ Function for logging messages
290
+ """
291
+ import time as time_module
292
+
293
+ def _log(msg):
294
+ if print_callback:
295
+ print_callback(msg)
296
+
297
+ def _progress(pct, message=None):
298
+ if progress_callback:
299
+ try:
300
+ progress_callback(pct, message)
301
+ except TypeError:
302
+ # Old-style callback without message parameter
303
+ try:
304
+ progress_callback(pct)
305
+ except Exception:
306
+ pass
307
+ except Exception:
308
+ pass
309
+
310
  jobs = {}
311
  job_status = {key: "QUEUED" for key in jobs}
312
+
313
+ total_circuits = len(all_circuits)
314
+ _log(f"Step 2: Submitting {total_circuits} circuit(s) for execution...")
315
+ _progress(40, f"Submitting {total_circuits} circuits...")
 
 
 
316
 
317
  if simulation=="True":
318
  backend = Aer.get_backend('qasm_simulator')
319
+ _log(f"Using simulator backend: {backend.name()}")
320
+ _progress(42, f"Backend: {backend.name()}")
321
+
322
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
323
  with Batch(backend=backend, max_time="2h") as batch:
324
  estimator = Estimator()
325
+ for idx, (key, qc) in enumerate(all_circuits.items()):
 
 
326
  qc = transpile(qc, basis_gates=['u', 'cx', 'rz', 'sx', 'x'])
327
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
328
  qc_compiled = pm.run(qc)
 
330
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
331
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
332
  jobs[key] = job
333
+
334
+ # Progress: 42-50% for submission
335
+ submit_pct = 42 + ((idx + 1) / total_circuits) * 8
336
+ _progress(submit_pct, f"Submitted circuit {idx+1}/{total_circuits}")
337
+
338
  elif simulation == "False" and platform == "IBM":
339
+ _log("Connecting to IBM Quantum service...")
340
+ _progress(42, "Connecting to IBM Quantum...")
341
+
342
  service = QiskitRuntimeService(channel="ibm_cloud",
343
  token="3Upysb5eOsRuBFlML0KV0uO4Jv4Lq01MhYsJ_Nu1pszy",
344
  instance="crn:v1:bluemix:public:quantum-computing:us-east:a/15157e4350c04a9dab51b8b8a4a93c86:e29afd91-64bf-4a82-8dbf-731e6c213595::")
345
  backend = service.least_busy(operational=True, simulator=False)
346
+ _log(f"Selected IBM QPU backend: {backend.name}")
347
+ _progress(45, f"Backend: {backend.name}")
348
+
349
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
350
  with Batch(backend=backend, max_time="2h") as batch:
351
  estimator = Estimator()
 
356
  estimator.options.resilience.zne_mitigation = True
357
  estimator.options.resilience.zne.noise_factors = (1.1, 1.3, 1.5)
358
  estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
359
+
360
+ for idx, (key, qc) in enumerate(all_circuits.items()):
 
361
  qc = transpile(qc, basis_gates=['u', 'cx', 'rz', 'sx', 'x'])
362
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
363
  qc_compiled = pm.run(qc)
 
365
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
366
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
367
  jobs[key] = job
368
+
369
+ # Progress: 45-55% for submission
370
+ submit_pct = 45 + ((idx + 1) / total_circuits) * 10
371
+ _progress(submit_pct, f"Submitted circuit {idx+1}/{total_circuits}")
372
+
373
  elif simulation == "False" and platform == "IONQ":
374
+ _log("Connecting to IonQ service...")
375
+ _progress(42, "Connecting to IonQ...")
 
376
 
377
  provider = IonQProvider(token = 'UzmBNbWXGKjyKrGyvwiw6E3rYhPdL8AU')
378
  ionq_backend = provider.get_backend("qpu.forte-enterprise-1")
379
+ _log(f"Selected IonQ backend: {ionq_backend.name()}")
380
+ _progress(45, f"Backend: {ionq_backend.name()}")
381
 
382
  pm = generate_preset_pass_manager(backend=ionq_backend, optimization_level=1)
383
  with Batch(backend=ionq_backend, max_time="2h") as batch:
384
  estimator = Estimator()
385
+ for idx, (key, qc) in enumerate(all_circuits.items()):
386
  pauli_label = 'Z'+'I'*(qc.num_qubits-1)
387
+ qc_compiled = transpile(qc,basis_gates=['ry','rz','rx','h','cx'])
388
  layout = qc_compiled.layout
389
  observable = SparsePauliOp(Pauli(pauli_label)).apply_layout(layout)
390
  job = estimator.run([(qc_compiled,observable)], precision=1/np.sqrt(shots))
391
  jobs[key] = job
392
+
393
+ submit_pct = 45 + ((idx + 1) / total_circuits) * 10
394
+ _progress(submit_pct, f"Submitted circuit {idx+1}/{total_circuits}")
395
+
396
+ # Poll for job completion with status updates (55-85%)
397
+ _log("Jobs submitted. Waiting for results...")
398
+ _progress(55, "Jobs queued, waiting for execution...")
399
+
400
+ total_jobs = len(jobs)
401
+ completed_jobs = 0
402
+ job_keys = list(jobs.keys())
403
+ pending_keys = set(job_keys)
404
+
405
+ poll_count = 0
406
+ max_polls = 600 # ~10 minutes with 1s interval
407
+
408
+ while pending_keys and poll_count < max_polls:
409
+ for key in list(pending_keys):
410
+ try:
411
+ job = jobs[key]
412
+ status = job.status()
413
+ status_name = status.name if hasattr(status, 'name') else str(status)
414
+
415
+ if status_name != job_status.get(key):
416
+ job_status[key] = status_name
417
+ _log(f"Job {key[:30]}... Status: {status_name}")
418
+
419
+ if status_name in ('DONE', 'ERROR', 'CANCELLED'):
420
+ pending_keys.discard(key)
421
+ completed_jobs += 1
422
+ # Progress: 55-85% based on completion
423
+ completion_pct = 55 + (completed_jobs / total_jobs) * 30
424
+ _progress(completion_pct, f"Completed {completed_jobs}/{total_jobs} jobs")
425
+
426
+ except Exception as e:
427
+ pass
428
+
429
+ if pending_keys:
430
+ # Show indeterminate progress while waiting
431
+ queued_count = sum(1 for s in job_status.values() if s in ('QUEUED', 'VALIDATING', 'INITIALIZING'))
432
+ running_count = sum(1 for s in job_status.values() if s == 'RUNNING')
433
+
434
+ if running_count > 0:
435
+ # Slowly increment while running (55-85 range)
436
+ run_progress = 55 + min(30, poll_count * 0.1)
437
+ _progress(run_progress, f"Running... ({running_count} active, {queued_count} queued)")
438
+ elif queued_count > 0:
439
+ queue_progress = 55 + min(10, poll_count * 0.05)
440
+ _progress(queue_progress, f"Queued... (waiting {poll_count}s)")
441
+
442
+ time_module.sleep(1)
443
+ poll_count += 1
444
+
445
+ _log("All jobs completed. Retrieving results...")
446
+ _progress(87, "Retrieving results...")
447
+
448
  #########################################################################################################
449
  results = {}
450
+ for idx, (key, job) in enumerate(jobs.items()):
451
  res = job.result()[0]
452
  z_exp = res.data.evs.item() # expectation value
453
  results[key] = np.sqrt((1 - z_exp) / 2)
454
+
455
+ # Progress: 87-90% for result retrieval
456
+ result_pct = 87 + ((idx + 1) / total_jobs) * 3
457
+ _progress(result_pct, f"Retrieved result {idx+1}/{total_jobs}")
458
+
459
+ _progress(90, "All results retrieved")
460
  #########################################################################################
461
  return results
462
 
 
695
  except Exception:
696
  pass
697
 
698
+ results = get_absolute_field_values(all_circuits, shots, pm_optimization_level, simulation, platform, progress_callback=progress_callback, print_callback=print_callback)
699
 
700
  # --- Step 3: Result Processing (90-100%) ---
701
  _log("Step 3: Result Processing")