Spaces:
Runtime error
Runtime error
harishaseebat92
commited on
Commit
·
841a0ef
1
Parent(s):
33cec4b
Runtime Version and QLBM Upload Option
Browse files- em/simulation.py +203 -51
- em/utils.py +0 -3
- qlbm_embedded.py +728 -146
em/simulation.py
CHANGED
|
@@ -5,6 +5,9 @@ Contains simulation logic including run_simulation_only, reset_to_defaults,
|
|
| 5 |
and stop handlers.
|
| 6 |
"""
|
| 7 |
import re
|
|
|
|
|
|
|
|
|
|
| 8 |
import numpy as np
|
| 9 |
|
| 10 |
from .state import state, ctrl, _apply_workflow_highlights, is_statevector_estimator_selected, is_ibm_qpu_selected
|
|
@@ -36,6 +39,77 @@ except ModuleNotFoundError:
|
|
| 36 |
)
|
| 37 |
import utils.delta_impulse_generator as qutils
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
__all__ = [
|
| 40 |
"run_simulation_only",
|
| 41 |
"reset_to_defaults",
|
|
@@ -239,11 +313,42 @@ def redraw_surface_plot():
|
|
| 239 |
ctrl.view_update()
|
| 240 |
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
def run_simulation_only():
|
| 243 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
from . import globals as g
|
| 245 |
from .excitation import nearest_node_index
|
| 246 |
from .qpu import build_qpu_timeseries_plotly_multi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
# Require selections before running
|
| 249 |
if not state.geometry_selection:
|
|
@@ -255,6 +360,7 @@ def run_simulation_only():
|
|
| 255 |
state.show_progress = False
|
| 256 |
state.is_running = False
|
| 257 |
state.run_button_text = "RUN!"
|
|
|
|
| 258 |
return
|
| 259 |
|
| 260 |
if not state.dist_type:
|
|
@@ -266,6 +372,7 @@ def run_simulation_only():
|
|
| 266 |
state.show_progress = False
|
| 267 |
state.is_running = False
|
| 268 |
state.run_button_text = "RUN!"
|
|
|
|
| 269 |
return
|
| 270 |
|
| 271 |
# Show status: Starting simulation
|
|
@@ -275,13 +382,20 @@ def run_simulation_only():
|
|
| 275 |
state.status_type = "info"
|
| 276 |
state.show_progress = True
|
| 277 |
state.simulation_progress = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
|
|
|
|
|
|
| 279 |
last_logged_percent = [0]
|
| 280 |
def _progress_callback(percent):
|
| 281 |
state.simulation_progress = percent
|
| 282 |
if percent - last_logged_percent[0] >= 10:
|
| 283 |
log_to_console(f"Simulation progress: {int(percent)}%")
|
| 284 |
last_logged_percent[0] = percent
|
|
|
|
| 285 |
|
| 286 |
# Reset stop flag and enable Stop button at start
|
| 287 |
set_stop_simulation(False)
|
|
@@ -293,10 +407,9 @@ def run_simulation_only():
|
|
| 293 |
state.is_running = True
|
| 294 |
state.simulation_has_run = False
|
| 295 |
state.run_button_text = "Running"
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
pass
|
| 300 |
|
| 301 |
nx, T = int(state.nx), float(state.T)
|
| 302 |
na, R = 1, 4
|
|
@@ -304,6 +417,8 @@ def run_simulation_only():
|
|
| 304 |
try:
|
| 305 |
state.status_message = "Creating initial state..."
|
| 306 |
state.simulation_progress = 10
|
|
|
|
|
|
|
| 307 |
if state.dist_type == "Delta":
|
| 308 |
initial_state = create_impulse_state_from_pos(
|
| 309 |
(nx, nx),
|
|
@@ -325,6 +440,9 @@ def run_simulation_only():
|
|
| 325 |
state.is_running = False
|
| 326 |
state.run_button_text = "RUN!"
|
| 327 |
state.stop_button_disabled = True
|
|
|
|
|
|
|
|
|
|
| 328 |
return
|
| 329 |
|
| 330 |
sve_selected = is_statevector_estimator_selected()
|
|
@@ -335,6 +453,8 @@ def run_simulation_only():
|
|
| 335 |
log_to_console("Running Statevector Estimator...")
|
| 336 |
state.status_message = "Running Statevector Estimator simulation..."
|
| 337 |
state.simulation_progress = 20
|
|
|
|
|
|
|
| 338 |
state.qpu_ts_ready = False
|
| 339 |
state.qpu_plot_style = "display: none; width: 900px; height: 660px; margin: 0 auto;"
|
| 340 |
state.qpu_ts_other_ready = False
|
|
@@ -361,6 +481,7 @@ def run_simulation_only():
|
|
| 361 |
|
| 362 |
state.status_message = "Building Statevector Estimator time series..."
|
| 363 |
state.simulation_progress = 60
|
|
|
|
| 364 |
|
| 365 |
def _sve_series_runner(field_type, positions, total_time, snapshot_dt, nx, impulse_pos, progress_callback=None, print_callback=None):
|
| 366 |
return qutils.run_sve(
|
|
@@ -377,13 +498,16 @@ def run_simulation_only():
|
|
| 377 |
print_callback=print_callback,
|
| 378 |
)
|
| 379 |
|
| 380 |
-
#
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
|
|
|
|
|
|
|
|
|
| 387 |
qpu_ts_cache["fig"] = fig
|
| 388 |
|
| 389 |
try:
|
|
@@ -425,10 +549,9 @@ def run_simulation_only():
|
|
| 425 |
finally:
|
| 426 |
state.is_running = False
|
| 427 |
state.stop_button_disabled = True
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
pass
|
| 432 |
return
|
| 433 |
|
| 434 |
# IBM QPU branch
|
|
@@ -438,10 +561,7 @@ def run_simulation_only():
|
|
| 438 |
log_to_console("Running IBM QPU simulation...")
|
| 439 |
state.status_message = "Running IBM QPU simulation..."
|
| 440 |
state.simulation_progress = 5
|
| 441 |
-
|
| 442 |
-
state.qpu_plot_style = "display: none; width: 900px; height: 660px; margin: 0 auto;"
|
| 443 |
-
state.qpu_ts_other_ready = False
|
| 444 |
-
state.qpu_other_plot_style = "display: none; width: 900px; height: 660px; margin: 0 auto;"
|
| 445 |
|
| 446 |
# Import IBM QPU backend
|
| 447 |
try:
|
|
@@ -476,34 +596,38 @@ def run_simulation_only():
|
|
| 476 |
|
| 477 |
state.status_message = "Step 1: Circuit Construction & Optimization (0-40%)..."
|
| 478 |
state.simulation_progress = 10
|
|
|
|
| 479 |
|
| 480 |
def _ibm_progress_callback(pct):
|
| 481 |
state.simulation_progress = int(pct)
|
| 482 |
-
# Update status message based on progress stage
|
| 483 |
if pct < 40:
|
| 484 |
state.status_message = f"Step 1: Circuit Construction & Optimization ({int(pct)}%)"
|
| 485 |
elif pct < 90:
|
| 486 |
state.status_message = f"Step 2: Circuit Execution ({int(pct)}%)"
|
| 487 |
else:
|
| 488 |
state.status_message = f"Step 3: Result Processing ({int(pct)}%)"
|
|
|
|
| 489 |
|
| 490 |
-
# Call the IBM QPU get_field_values function
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
# Build time frames to match the output
|
| 509 |
times = ibm_create_time_frames(float(T), snapshot_dt)
|
|
@@ -578,6 +702,7 @@ def run_simulation_only():
|
|
| 578 |
log_to_console("IBM QPU run completed")
|
| 579 |
state.status_type = "success"
|
| 580 |
state.show_progress = False
|
|
|
|
| 581 |
|
| 582 |
ready = bool(field_values) and len(field_values) > 0
|
| 583 |
state.qpu_ts_ready = ready
|
|
@@ -613,10 +738,9 @@ def run_simulation_only():
|
|
| 613 |
finally:
|
| 614 |
state.is_running = False
|
| 615 |
state.stop_button_disabled = True
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
pass
|
| 620 |
return
|
| 621 |
|
| 622 |
# IonQ QPU placeholder branch (not yet implemented)
|
|
@@ -629,16 +753,18 @@ def run_simulation_only():
|
|
| 629 |
state.run_button_text = "RUN!"
|
| 630 |
state.stop_button_disabled = True
|
| 631 |
log_to_console("IonQ QPU backend not connected. Use IBM QPU or Statevector Estimator instead.")
|
|
|
|
| 632 |
try:
|
| 633 |
ctrl.view_update()
|
| 634 |
except Exception:
|
| 635 |
pass
|
| 636 |
return
|
| 637 |
|
| 638 |
-
# Simulator path
|
| 639 |
log_to_console("Running simulation...")
|
| 640 |
state.status_message = "Running simulation... This may take a while."
|
| 641 |
state.simulation_progress = 30
|
|
|
|
| 642 |
|
| 643 |
snapshot_dt = float(state.dt_user)
|
| 644 |
|
|
@@ -646,19 +772,40 @@ def run_simulation_only():
|
|
| 646 |
return g.stop_simulation
|
| 647 |
|
| 648 |
state.simulation_progress = 50
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
g.simulation_data = sim_data
|
| 657 |
g.snapshot_times = times
|
| 658 |
log_to_console("Simulation complete.")
|
| 659 |
|
| 660 |
state.simulation_progress = 80
|
| 661 |
state.status_message = "Processing simulation results..."
|
|
|
|
| 662 |
|
| 663 |
if sim_data.size > 0:
|
| 664 |
setup_surface_plot_data(sim_data, nx)
|
|
@@ -678,6 +825,11 @@ def run_simulation_only():
|
|
| 678 |
|
| 679 |
state.is_running = False
|
| 680 |
state.stop_button_disabled = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
|
| 682 |
|
| 683 |
def reset_to_defaults():
|
|
@@ -1059,4 +1211,4 @@ def update_value_display(point):
|
|
| 1059 |
text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nValue: {value:.3e}"
|
| 1060 |
|
| 1061 |
plotter.add_text(text, name="value_text", position="lower_left", color="black", font_size=10)
|
| 1062 |
-
ctrl.view_update()
|
|
|
|
| 5 |
and stop handlers.
|
| 6 |
"""
|
| 7 |
import re
|
| 8 |
+
import asyncio
|
| 9 |
+
import threading
|
| 10 |
+
import time
|
| 11 |
import numpy as np
|
| 12 |
|
| 13 |
from .state import state, ctrl, _apply_workflow_highlights, is_statevector_estimator_selected, is_ibm_qpu_selected
|
|
|
|
| 39 |
)
|
| 40 |
import utils.delta_impulse_generator as qutils
|
| 41 |
|
| 42 |
+
# --- Module-level async infrastructure ---
|
| 43 |
+
_heartbeat_thread = None
|
| 44 |
+
_heartbeat_on = False
|
| 45 |
+
_sim_start_time = None
|
| 46 |
+
_simulation_executor = None # Thread pool for async execution
|
| 47 |
+
_main_loop = None # Reference to main event loop for thread-safe callbacks
|
| 48 |
+
|
| 49 |
+
def _get_server():
|
| 50 |
+
"""Get the trame server from state module."""
|
| 51 |
+
from .state import get_server
|
| 52 |
+
return get_server()
|
| 53 |
+
|
| 54 |
+
def _flush_state():
|
| 55 |
+
"""Force state flush to browser (synchronous, for main thread use)."""
|
| 56 |
+
try:
|
| 57 |
+
server = _get_server()
|
| 58 |
+
if server:
|
| 59 |
+
server.state.flush()
|
| 60 |
+
except Exception:
|
| 61 |
+
pass
|
| 62 |
+
|
| 63 |
+
def _flush_state_threadsafe():
|
| 64 |
+
"""
|
| 65 |
+
Thread-safe state flush - schedules flush on the main event loop.
|
| 66 |
+
Use this from background threads (e.g., inside executor callbacks).
|
| 67 |
+
"""
|
| 68 |
+
global _main_loop
|
| 69 |
+
try:
|
| 70 |
+
server = _get_server()
|
| 71 |
+
if server and _main_loop is not None and _main_loop.is_running():
|
| 72 |
+
# Schedule the flush on the main event loop
|
| 73 |
+
_main_loop.call_soon_threadsafe(server.state.flush)
|
| 74 |
+
elif server:
|
| 75 |
+
# Fallback: direct flush (may not work from threads)
|
| 76 |
+
server.state.flush()
|
| 77 |
+
except Exception:
|
| 78 |
+
pass
|
| 79 |
+
|
| 80 |
+
async def _flush_async():
|
| 81 |
+
"""Async helper to flush state and yield to event loop."""
|
| 82 |
+
_flush_state()
|
| 83 |
+
await asyncio.sleep(0) # Yield control to event loop
|
| 84 |
+
|
| 85 |
+
def _start_progress_heartbeat():
|
| 86 |
+
"""Start background thread for continuous progress updates."""
|
| 87 |
+
global _heartbeat_thread, _heartbeat_on, _sim_start_time
|
| 88 |
+
|
| 89 |
+
if _heartbeat_thread and _heartbeat_thread.is_alive():
|
| 90 |
+
return
|
| 91 |
+
|
| 92 |
+
_sim_start_time = time.time()
|
| 93 |
+
|
| 94 |
+
def loop_fn():
|
| 95 |
+
global _heartbeat_on
|
| 96 |
+
while _heartbeat_on:
|
| 97 |
+
if state.is_running and _sim_start_time is not None:
|
| 98 |
+
elapsed = time.time() - _sim_start_time
|
| 99 |
+
state.simulation_elapsed = elapsed
|
| 100 |
+
_flush_state_threadsafe() # Use thread-safe version
|
| 101 |
+
time.sleep(0.1) # Update every 100ms
|
| 102 |
+
|
| 103 |
+
_heartbeat_on = True
|
| 104 |
+
_heartbeat_thread = threading.Thread(target=loop_fn, daemon=True)
|
| 105 |
+
_heartbeat_thread.start()
|
| 106 |
+
|
| 107 |
+
def _stop_progress_heartbeat():
|
| 108 |
+
"""Stop the background heartbeat thread."""
|
| 109 |
+
global _heartbeat_on, _heartbeat_thread
|
| 110 |
+
_heartbeat_on = False
|
| 111 |
+
_heartbeat_thread = None
|
| 112 |
+
|
| 113 |
__all__ = [
|
| 114 |
"run_simulation_only",
|
| 115 |
"reset_to_defaults",
|
|
|
|
| 313 |
ctrl.view_update()
|
| 314 |
|
| 315 |
|
| 316 |
+
# ---------------------------------------------------------------------------
|
| 317 |
+
# Async Simulation Runner with Full Async Pattern
|
| 318 |
+
# ---------------------------------------------------------------------------
|
| 319 |
+
|
| 320 |
def run_simulation_only():
|
| 321 |
+
"""
|
| 322 |
+
Entry point for simulation - launches the async worker.
|
| 323 |
+
This is called by the UI button click and schedules the async task.
|
| 324 |
+
"""
|
| 325 |
+
server = _get_server()
|
| 326 |
+
if server is None:
|
| 327 |
+
log_to_console("Error: Server not available")
|
| 328 |
+
return
|
| 329 |
+
|
| 330 |
+
# Schedule the async simulation
|
| 331 |
+
asyncio.ensure_future(_run_simulation_async())
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
async def _run_simulation_async():
|
| 335 |
+
"""
|
| 336 |
+
Async simulation runner that uses thread pool for blocking work.
|
| 337 |
+
This allows the UI to update in real-time during simulation.
|
| 338 |
+
"""
|
| 339 |
+
global _main_loop
|
| 340 |
+
|
| 341 |
from . import globals as g
|
| 342 |
from .excitation import nearest_node_index
|
| 343 |
from .qpu import build_qpu_timeseries_plotly_multi
|
| 344 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 345 |
+
|
| 346 |
+
# Capture the main event loop for thread-safe callbacks
|
| 347 |
+
_main_loop = asyncio.get_event_loop()
|
| 348 |
+
|
| 349 |
+
# Create executor for blocking operations
|
| 350 |
+
executor = ThreadPoolExecutor(max_workers=1)
|
| 351 |
+
loop = _main_loop
|
| 352 |
|
| 353 |
# Require selections before running
|
| 354 |
if not state.geometry_selection:
|
|
|
|
| 360 |
state.show_progress = False
|
| 361 |
state.is_running = False
|
| 362 |
state.run_button_text = "RUN!"
|
| 363 |
+
await _flush_async()
|
| 364 |
return
|
| 365 |
|
| 366 |
if not state.dist_type:
|
|
|
|
| 372 |
state.show_progress = False
|
| 373 |
state.is_running = False
|
| 374 |
state.run_button_text = "RUN!"
|
| 375 |
+
await _flush_async()
|
| 376 |
return
|
| 377 |
|
| 378 |
# Show status: Starting simulation
|
|
|
|
| 382 |
state.status_type = "info"
|
| 383 |
state.show_progress = True
|
| 384 |
state.simulation_progress = 0
|
| 385 |
+
await _flush_async()
|
| 386 |
+
|
| 387 |
+
# Start heartbeat for continuous elapsed time updates
|
| 388 |
+
_start_progress_heartbeat()
|
| 389 |
|
| 390 |
+
# Progress callback that updates state (called from worker thread)
|
| 391 |
+
# Uses thread-safe flush to push updates to browser
|
| 392 |
last_logged_percent = [0]
|
| 393 |
def _progress_callback(percent):
|
| 394 |
state.simulation_progress = percent
|
| 395 |
if percent - last_logged_percent[0] >= 10:
|
| 396 |
log_to_console(f"Simulation progress: {int(percent)}%")
|
| 397 |
last_logged_percent[0] = percent
|
| 398 |
+
_flush_state_threadsafe() # Thread-safe flush!
|
| 399 |
|
| 400 |
# Reset stop flag and enable Stop button at start
|
| 401 |
set_stop_simulation(False)
|
|
|
|
| 407 |
state.is_running = True
|
| 408 |
state.simulation_has_run = False
|
| 409 |
state.run_button_text = "Running"
|
| 410 |
+
|
| 411 |
+
# Initial flush to show "Running" state
|
| 412 |
+
_flush_state()
|
|
|
|
| 413 |
|
| 414 |
nx, T = int(state.nx), float(state.T)
|
| 415 |
na, R = 1, 4
|
|
|
|
| 417 |
try:
|
| 418 |
state.status_message = "Creating initial state..."
|
| 419 |
state.simulation_progress = 10
|
| 420 |
+
_flush_state()
|
| 421 |
+
|
| 422 |
if state.dist_type == "Delta":
|
| 423 |
initial_state = create_impulse_state_from_pos(
|
| 424 |
(nx, nx),
|
|
|
|
| 440 |
state.is_running = False
|
| 441 |
state.run_button_text = "RUN!"
|
| 442 |
state.stop_button_disabled = True
|
| 443 |
+
_stop_progress_heartbeat()
|
| 444 |
+
await _flush_async()
|
| 445 |
+
executor.shutdown(wait=False)
|
| 446 |
return
|
| 447 |
|
| 448 |
sve_selected = is_statevector_estimator_selected()
|
|
|
|
| 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
|
| 459 |
state.qpu_plot_style = "display: none; width: 900px; height: 660px; margin: 0 auto;"
|
| 460 |
state.qpu_ts_other_ready = False
|
|
|
|
| 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(
|
|
|
|
| 498 |
print_callback=print_callback,
|
| 499 |
)
|
| 500 |
|
| 501 |
+
# Run SVE in executor to keep UI responsive
|
| 502 |
+
def _run_sve_blocking():
|
| 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:
|
|
|
|
| 549 |
finally:
|
| 550 |
state.is_running = False
|
| 551 |
state.stop_button_disabled = True
|
| 552 |
+
_stop_progress_heartbeat()
|
| 553 |
+
await _flush_async()
|
| 554 |
+
executor.shutdown(wait=False)
|
|
|
|
| 555 |
return
|
| 556 |
|
| 557 |
# IBM QPU branch
|
|
|
|
| 561 |
log_to_console("Running IBM QPU simulation...")
|
| 562 |
state.status_message = "Running IBM QPU simulation..."
|
| 563 |
state.simulation_progress = 5
|
| 564 |
+
await _flush_async()
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
# Import IBM QPU backend
|
| 567 |
try:
|
|
|
|
| 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
|
| 612 |
+
def _run_ibm_qpu():
|
| 613 |
+
return ibm_get_field_values(
|
| 614 |
+
field=field_type,
|
| 615 |
+
x=monitor_x,
|
| 616 |
+
y=monitor_y,
|
| 617 |
+
T=float(T),
|
| 618 |
+
snapshot_time=snapshot_dt,
|
| 619 |
+
nx=nx,
|
| 620 |
+
impulse_pos=impulse_pos,
|
| 621 |
+
shots=10000,
|
| 622 |
+
pm_optimization_level=2,
|
| 623 |
+
simulation="True",
|
| 624 |
+
optimization="True",
|
| 625 |
+
platform="IBM",
|
| 626 |
+
progress_callback=_ibm_progress_callback,
|
| 627 |
+
print_callback=log_to_console,
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
field_values = await loop.run_in_executor(executor, _run_ibm_qpu)
|
| 631 |
|
| 632 |
# Build time frames to match the output
|
| 633 |
times = ibm_create_time_frames(float(T), snapshot_dt)
|
|
|
|
| 702 |
log_to_console("IBM QPU run completed")
|
| 703 |
state.status_type = "success"
|
| 704 |
state.show_progress = False
|
| 705 |
+
await _flush_async() # Update UI with completion status
|
| 706 |
|
| 707 |
ready = bool(field_values) and len(field_values) > 0
|
| 708 |
state.qpu_ts_ready = ready
|
|
|
|
| 738 |
finally:
|
| 739 |
state.is_running = False
|
| 740 |
state.stop_button_disabled = True
|
| 741 |
+
_stop_progress_heartbeat()
|
| 742 |
+
executor.shutdown(wait=False)
|
| 743 |
+
await _flush_async()
|
|
|
|
| 744 |
return
|
| 745 |
|
| 746 |
# IonQ QPU placeholder branch (not yet implemented)
|
|
|
|
| 753 |
state.run_button_text = "RUN!"
|
| 754 |
state.stop_button_disabled = True
|
| 755 |
log_to_console("IonQ QPU backend not connected. Use IBM QPU or Statevector Estimator instead.")
|
| 756 |
+
await _flush_async()
|
| 757 |
try:
|
| 758 |
ctrl.view_update()
|
| 759 |
except Exception:
|
| 760 |
pass
|
| 761 |
return
|
| 762 |
|
| 763 |
+
# Simulator path - run blocking simulation in executor
|
| 764 |
log_to_console("Running simulation...")
|
| 765 |
state.status_message = "Running simulation... This may take a while."
|
| 766 |
state.simulation_progress = 30
|
| 767 |
+
await _flush_async()
|
| 768 |
|
| 769 |
snapshot_dt = float(state.dt_user)
|
| 770 |
|
|
|
|
| 772 |
return g.stop_simulation
|
| 773 |
|
| 774 |
state.simulation_progress = 50
|
| 775 |
+
await _flush_async()
|
| 776 |
+
|
| 777 |
+
# Run the blocking simulation in a thread pool to keep UI responsive
|
| 778 |
+
def _run_blocking_sim():
|
| 779 |
+
return run_sim(
|
| 780 |
+
nx, na, R, initial_state, T,
|
| 781 |
+
snapshot_dt=snapshot_dt,
|
| 782 |
+
stop_check=_stop_check,
|
| 783 |
+
progress_callback=_progress_callback,
|
| 784 |
+
print_callback=log_to_console
|
| 785 |
+
)
|
| 786 |
+
|
| 787 |
+
try:
|
| 788 |
+
sim_data, times = await loop.run_in_executor(executor, _run_blocking_sim)
|
| 789 |
+
except Exception as e:
|
| 790 |
+
state.error_message = f"Simulation error: {e}"
|
| 791 |
+
state.status_message = f"Error: {e}"
|
| 792 |
+
state.status_type = "error"
|
| 793 |
+
state.show_progress = False
|
| 794 |
+
state.is_running = False
|
| 795 |
+
state.run_button_text = "RUN!"
|
| 796 |
+
state.stop_button_disabled = True
|
| 797 |
+
_stop_progress_heartbeat()
|
| 798 |
+
await _flush_async()
|
| 799 |
+
executor.shutdown(wait=False)
|
| 800 |
+
return
|
| 801 |
+
|
| 802 |
g.simulation_data = sim_data
|
| 803 |
g.snapshot_times = times
|
| 804 |
log_to_console("Simulation complete.")
|
| 805 |
|
| 806 |
state.simulation_progress = 80
|
| 807 |
state.status_message = "Processing simulation results..."
|
| 808 |
+
await _flush_async()
|
| 809 |
|
| 810 |
if sim_data.size > 0:
|
| 811 |
setup_surface_plot_data(sim_data, nx)
|
|
|
|
| 825 |
|
| 826 |
state.is_running = False
|
| 827 |
state.stop_button_disabled = True
|
| 828 |
+
_stop_progress_heartbeat()
|
| 829 |
+
await _flush_async()
|
| 830 |
+
|
| 831 |
+
# Cleanup executor
|
| 832 |
+
executor.shutdown(wait=False)
|
| 833 |
|
| 834 |
|
| 835 |
def reset_to_defaults():
|
|
|
|
| 1211 |
text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nValue: {value:.3e}"
|
| 1212 |
|
| 1213 |
plotter.add_text(text, name="value_text", position="lower_left", color="black", font_size=10)
|
| 1214 |
+
ctrl.view_update()
|
em/utils.py
CHANGED
|
@@ -120,9 +120,6 @@ def load_logo_data_uri() -> str:
|
|
| 120 |
base_dir = os.path.dirname(os.path.dirname(__file__)) # quantum_embedded folder
|
| 121 |
candidates = [
|
| 122 |
os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg"),
|
| 123 |
-
os.path.join(base_dir, "synopsys-logo-color-rgb.svg"),
|
| 124 |
-
os.path.join(base_dir, "synopsys-logo-color-rgb.png"),
|
| 125 |
-
os.path.join(base_dir, "synopsys-logo-color-rgb.jpg"),
|
| 126 |
# Also check in parent quantum folder
|
| 127 |
os.path.join(os.path.dirname(base_dir), "quantum", "ansys-part-of-synopsys-logo.svg"),
|
| 128 |
]
|
|
|
|
| 120 |
base_dir = os.path.dirname(os.path.dirname(__file__)) # quantum_embedded folder
|
| 121 |
candidates = [
|
| 122 |
os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg"),
|
|
|
|
|
|
|
|
|
|
| 123 |
# Also check in parent quantum folder
|
| 124 |
os.path.join(os.path.dirname(base_dir), "quantum", "ansys-part-of-synopsys-logo.svg"),
|
| 125 |
]
|
qlbm_embedded.py
CHANGED
|
@@ -12,6 +12,11 @@ import math
|
|
| 12 |
import pyvista as pv
|
| 13 |
import plotly.graph_objects as go
|
| 14 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from pathlib import Path
|
| 16 |
from datetime import datetime
|
| 17 |
from trame_vuetify.widgets import vuetify3
|
|
@@ -25,6 +30,7 @@ pv.OFF_SCREEN = True
|
|
| 25 |
# --- Qiskit Backend Detection ---
|
| 26 |
_QISKIT_BACKEND_AVAILABLE = False
|
| 27 |
_QISKIT_IMPORT_ERROR = None
|
|
|
|
| 28 |
|
| 29 |
try:
|
| 30 |
from qlbm.qlbm_sample_app import (
|
|
@@ -41,6 +47,20 @@ except ImportError as e:
|
|
| 41 |
_QISKIT_IMPORT_ERROR = str(e)
|
| 42 |
print(f"Qiskit backend not available: {e}")
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
# --- CUDA-Q Backend Detection ---
|
| 45 |
def _env_flag(name: str) -> bool:
|
| 46 |
return os.environ.get(name, "").strip().lower() in ("1", "true", "yes")
|
|
@@ -90,6 +110,69 @@ simulation_data_frames = []
|
|
| 90 |
simulation_times = []
|
| 91 |
current_grid_object = None
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
GRID_SIZES = [8, 16, 32, 64, 128, 256]
|
| 94 |
_WORKFLOW_BASE_STYLE = "font-size: 0.8rem; border: 1px solid transparent; transition: box-shadow 0.2s ease;"
|
| 95 |
_WORKFLOW_HIGHLIGHT_STYLE = "font-size: 0.8rem; box-shadow: 0 0 0 2px #6200ea;"
|
|
@@ -213,6 +296,18 @@ def init_state():
|
|
| 213 |
"qlbm_qiskit_mode": False, # True when using Qiskit backend (shows Plotly slider)
|
| 214 |
"qlbm_qiskit_backend_available": _QISKIT_BACKEND_AVAILABLE,
|
| 215 |
"qlbm_qiskit_fig": None, # Stores the Plotly figure for Qiskit results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
})
|
| 217 |
_initialized = True
|
| 218 |
|
|
@@ -1077,10 +1172,231 @@ def _run_qiskit_simulation(progress_callback=None):
|
|
| 1077 |
return output, fig, params["T_list"]
|
| 1078 |
|
| 1079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1080 |
# --- Main Simulation ---
|
| 1081 |
def run_simulation():
|
| 1082 |
-
"""
|
| 1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
|
| 1085 |
if not _SIMULATION_CAN_RUN:
|
| 1086 |
msg = _SIMULATION_DISABLED_REASON or "Simulation backend is not available on this platform."
|
|
@@ -1088,6 +1404,8 @@ def run_simulation():
|
|
| 1088 |
log_to_console(f"Error: {msg}")
|
| 1089 |
_state.qlbm_status_message = "Error: Backend unavailable"
|
| 1090 |
_state.qlbm_status_type = "error"
|
|
|
|
|
|
|
| 1091 |
return
|
| 1092 |
|
| 1093 |
_state.qlbm_is_running = True
|
|
@@ -1096,8 +1414,12 @@ def run_simulation():
|
|
| 1096 |
_state.qlbm_qiskit_mode = False # Reset Qiskit mode
|
| 1097 |
_state.qlbm_show_progress = True
|
| 1098 |
_state.qlbm_simulation_progress = 0
|
| 1099 |
-
_state.qlbm_status_message = "
|
| 1100 |
_state.qlbm_status_type = "info"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1101 |
|
| 1102 |
# Determine if using Qiskit backend
|
| 1103 |
use_qiskit = (
|
|
@@ -1126,21 +1448,27 @@ def run_simulation():
|
|
| 1126 |
log_to_console("Job Initiated")
|
| 1127 |
log_to_console(f"Grid Size: {_state.qlbm_grid_size}×{_state.qlbm_grid_size}×{_state.qlbm_grid_size}, Time Steps: {_state.qlbm_time_steps}, Distribution: {_state.qlbm_dist_type}, Boundary: {_state.qlbm_boundary_condition}, Backend: {backend_info}, Velocity: vx={_state.qlbm_vx_expr}, vy={_state.qlbm_vy_expr}, vz={_state.qlbm_vz_expr}")
|
| 1128 |
|
| 1129 |
-
|
|
|
|
| 1130 |
def _progress_callback(percent):
|
| 1131 |
-
nonlocal last_logged_percent
|
| 1132 |
_state.qlbm_simulation_progress = percent
|
| 1133 |
-
if percent - last_logged_percent >= 10:
|
| 1134 |
log_to_console(f"Simulation progress: {int(percent)}%")
|
| 1135 |
-
last_logged_percent = percent
|
|
|
|
| 1136 |
|
| 1137 |
try:
|
| 1138 |
# === Qiskit Backend (IBM Qiskit Simulator) ===
|
| 1139 |
if use_qiskit:
|
| 1140 |
log_to_console("Using IBM Qiskit Simulator backend...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1141 |
|
| 1142 |
-
|
| 1143 |
-
output, plotly_fig, T_list = _run_qiskit_simulation(progress_callback=_progress_callback)
|
| 1144 |
|
| 1145 |
# Store results
|
| 1146 |
simulation_data_frames = output
|
|
@@ -1156,14 +1484,17 @@ def run_simulation():
|
|
| 1156 |
_state.qlbm_simulation_has_run = True
|
| 1157 |
_state.qlbm_qiskit_mode = True # Use Plotly display instead of PyVista
|
| 1158 |
|
| 1159 |
-
|
| 1160 |
log_to_console("Qiskit simulation completed successfully.")
|
| 1161 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1162 |
_state.qlbm_status_type = "success"
|
|
|
|
| 1163 |
|
| 1164 |
# === IBM QPU Backend ===
|
| 1165 |
elif use_ibm_qpu:
|
| 1166 |
log_to_console("Using IBM QPU backend...")
|
|
|
|
|
|
|
| 1167 |
|
| 1168 |
params = _map_state_to_qiskit_params()
|
| 1169 |
if params is None:
|
|
@@ -1171,6 +1502,9 @@ def run_simulation():
|
|
| 1171 |
|
| 1172 |
# Create initial state circuit
|
| 1173 |
log_to_console("Creating initial state circuit...")
|
|
|
|
|
|
|
|
|
|
| 1174 |
init_state_prep_circ = get_named_init_state_circuit(
|
| 1175 |
n=params["n"],
|
| 1176 |
init_state_name=params["init_state_name"],
|
|
@@ -1187,24 +1521,37 @@ def run_simulation():
|
|
| 1187 |
)
|
| 1188 |
|
| 1189 |
log_to_console("Submitting job to IBM Quantum...")
|
|
|
|
|
|
|
|
|
|
| 1190 |
|
| 1191 |
-
# Run HW simulation
|
| 1192 |
-
|
| 1193 |
-
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
|
| 1202 |
-
|
| 1203 |
-
|
| 1204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1205 |
|
| 1206 |
-
|
| 1207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
|
| 1209 |
# Generate T=0 initial snapshot and prepend to output
|
| 1210 |
log_to_console("Generating T=0 initial distribution snapshot...")
|
|
@@ -1240,6 +1587,10 @@ def run_simulation():
|
|
| 1240 |
log_to_console(f"Warning: Could not generate T=0 snapshot: {exc}")
|
| 1241 |
extended_T_list = params["T_list"]
|
| 1242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1243 |
# Update UI
|
| 1244 |
if hasattr(_ctrl, "qlbm_qiskit_result_update"):
|
| 1245 |
_ctrl.qlbm_qiskit_result_update(plotly_fig)
|
|
@@ -1250,14 +1601,17 @@ def run_simulation():
|
|
| 1250 |
_state.qlbm_simulation_has_run = True
|
| 1251 |
_state.qlbm_qiskit_mode = True
|
| 1252 |
|
| 1253 |
-
|
| 1254 |
log_to_console("IBM QPU simulation completed successfully.")
|
| 1255 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1256 |
_state.qlbm_status_type = "success"
|
|
|
|
| 1257 |
|
| 1258 |
# === IonQ QPU Backend ===
|
| 1259 |
elif use_ionq_qpu:
|
| 1260 |
log_to_console("Using IonQ QPU backend...")
|
|
|
|
|
|
|
| 1261 |
|
| 1262 |
params = _map_state_to_qiskit_params()
|
| 1263 |
if params is None:
|
|
@@ -1265,6 +1619,9 @@ def run_simulation():
|
|
| 1265 |
|
| 1266 |
# Create initial state circuit
|
| 1267 |
log_to_console("Creating initial state circuit...")
|
|
|
|
|
|
|
|
|
|
| 1268 |
init_state_prep_circ = get_named_init_state_circuit(
|
| 1269 |
n=params["n"],
|
| 1270 |
init_state_name=params["init_state_name"],
|
|
@@ -1281,23 +1638,37 @@ def run_simulation():
|
|
| 1281 |
)
|
| 1282 |
|
| 1283 |
log_to_console("Submitting job to IonQ Quantum...")
|
|
|
|
|
|
|
|
|
|
| 1284 |
|
| 1285 |
-
# Run IonQ HW simulation
|
| 1286 |
-
|
| 1287 |
-
|
| 1288 |
-
|
| 1289 |
-
|
| 1290 |
-
|
| 1291 |
-
|
| 1292 |
-
|
| 1293 |
-
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
-
|
| 1297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1298 |
|
| 1299 |
-
|
| 1300 |
-
|
|
|
|
|
|
|
| 1301 |
|
| 1302 |
# Generate T=0 initial snapshot and prepend to output
|
| 1303 |
log_to_console("Generating T=0 initial distribution snapshot...")
|
|
@@ -1333,6 +1704,10 @@ def run_simulation():
|
|
| 1333 |
log_to_console(f"Warning: Could not generate T=0 snapshot: {exc}")
|
| 1334 |
extended_T_list = params["T_list"]
|
| 1335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1336 |
# Update UI
|
| 1337 |
if hasattr(_ctrl, "qlbm_qiskit_result_update"):
|
| 1338 |
_ctrl.qlbm_qiskit_result_update(plotly_fig)
|
|
@@ -1343,14 +1718,17 @@ def run_simulation():
|
|
| 1343 |
_state.qlbm_simulation_has_run = True
|
| 1344 |
_state.qlbm_qiskit_mode = True
|
| 1345 |
|
| 1346 |
-
|
| 1347 |
log_to_console("IonQ QPU simulation completed successfully.")
|
| 1348 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1349 |
_state.qlbm_status_type = "success"
|
|
|
|
| 1350 |
|
| 1351 |
# === CUDA-Q Backend ===
|
| 1352 |
elif _state.qlbm_backend_type == "Simulator" and _state.qlbm_selected_simulator == "CUDA-Q simulator":
|
| 1353 |
_state.qlbm_qiskit_mode = False # Use PyVista display
|
|
|
|
|
|
|
| 1354 |
|
| 1355 |
grid_size = int(_state.qlbm_grid_size)
|
| 1356 |
num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
|
|
@@ -1362,37 +1740,49 @@ def run_simulation():
|
|
| 1362 |
vy_func = make_velocity_func(_state.qlbm_vy_expr)
|
| 1363 |
vz_func = make_velocity_func(_state.qlbm_vz_expr)
|
| 1364 |
|
| 1365 |
-
|
|
|
|
| 1366 |
|
| 1367 |
if simulate_qlbm_3D_and_animate is not None:
|
| 1368 |
log_to_console("Running CUDA-Q Simulation...")
|
| 1369 |
-
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
-
|
| 1379 |
-
|
| 1380 |
-
|
| 1381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1382 |
else:
|
| 1383 |
# Fallback to CPU demo if CUDA-Q not available
|
| 1384 |
log_to_console("CUDA-Q not available, falling back to CPU Demo...")
|
| 1385 |
-
|
| 1386 |
-
|
| 1387 |
-
|
| 1388 |
-
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1394 |
|
| 1395 |
-
|
|
|
|
| 1396 |
|
| 1397 |
# Update plotter with results
|
| 1398 |
if grid_obj:
|
|
@@ -1421,15 +1811,19 @@ def run_simulation():
|
|
| 1421 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1422 |
_state.qlbm_status_type = "success"
|
| 1423 |
_state.qlbm_simulation_progress = 100
|
|
|
|
| 1424 |
else:
|
| 1425 |
_state.qlbm_run_error = "Simulation produced no data."
|
| 1426 |
log_to_console("Error: Simulation produced no data.")
|
| 1427 |
_state.qlbm_status_message = "Error: No data produced"
|
| 1428 |
_state.qlbm_status_type = "error"
|
|
|
|
| 1429 |
|
| 1430 |
# === CPU Demo Backend (for QPU or fallback) ===
|
| 1431 |
else:
|
| 1432 |
_state.qlbm_qiskit_mode = False # Use PyVista display
|
|
|
|
|
|
|
| 1433 |
|
| 1434 |
grid_size = int(_state.qlbm_grid_size)
|
| 1435 |
num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
|
|
@@ -1443,19 +1837,27 @@ def run_simulation():
|
|
| 1443 |
|
| 1444 |
_progress_callback(0)
|
| 1445 |
|
| 1446 |
-
|
|
|
|
|
|
|
|
|
|
| 1447 |
log_to_console("Running CPU Demo Simulation...")
|
| 1448 |
-
frames, times, grid_obj = _run_cpu_demo_simulation(
|
| 1449 |
-
grid_size=grid_size,
|
| 1450 |
-
T=T,
|
| 1451 |
-
distribution_type=distribution_type or "Sinusoidal",
|
| 1452 |
-
vx_func=vx_func,
|
| 1453 |
-
vy_func=vy_func,
|
| 1454 |
-
vz_func=vz_func,
|
| 1455 |
-
progress_callback=_progress_callback
|
| 1456 |
-
)
|
| 1457 |
|
| 1458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1459 |
|
| 1460 |
# Update plotter with results
|
| 1461 |
if grid_obj:
|
|
@@ -1484,11 +1886,13 @@ def run_simulation():
|
|
| 1484 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1485 |
_state.qlbm_status_type = "success"
|
| 1486 |
_state.qlbm_simulation_progress = 100
|
|
|
|
| 1487 |
else:
|
| 1488 |
_state.qlbm_run_error = "Simulation produced no data."
|
| 1489 |
log_to_console("Error: Simulation produced no data.")
|
| 1490 |
_state.qlbm_status_message = "Error: No data produced"
|
| 1491 |
_state.qlbm_status_type = "error"
|
|
|
|
| 1492 |
|
| 1493 |
except Exception as e:
|
| 1494 |
_state.qlbm_run_error = f"Simulation failed: {str(e)}"
|
|
@@ -1498,10 +1902,14 @@ def run_simulation():
|
|
| 1498 |
traceback.print_exc()
|
| 1499 |
_state.qlbm_status_message = "Simulation failed"
|
| 1500 |
_state.qlbm_status_type = "error"
|
|
|
|
| 1501 |
finally:
|
| 1502 |
_state.qlbm_is_running = False
|
|
|
|
|
|
|
| 1503 |
if _state.qlbm_status_type != "success":
|
| 1504 |
_state.qlbm_show_progress = False
|
|
|
|
| 1505 |
|
| 1506 |
|
| 1507 |
def stop_simulation():
|
|
@@ -1659,23 +2067,27 @@ def _build_control_panels(plotter):
|
|
| 1659 |
with vuetify3.VCardText():
|
| 1660 |
vuetify3.VDivider(classes="my-2")
|
| 1661 |
vuetify3.VCardSubtitle("Problems", classes="text-caption font-weight-bold mt-2")
|
| 1662 |
-
vuetify3.
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
|
| 1677 |
-
|
| 1678 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1679 |
|
| 1680 |
# Geometry card
|
| 1681 |
with vuetify3.VCard(classes="mb-2"):
|
|
@@ -1703,11 +2115,20 @@ def _build_control_panels(plotter):
|
|
| 1703 |
vuetify3.VCardSubtitle("Domain dimensions", classes="text-caption font-weight-bold mb-2")
|
| 1704 |
with vuetify3.VRow(dense=True):
|
| 1705 |
with vuetify3.VCol():
|
| 1706 |
-
vuetify3.
|
|
|
|
|
|
|
|
|
|
| 1707 |
with vuetify3.VCol():
|
| 1708 |
-
vuetify3.
|
|
|
|
|
|
|
|
|
|
| 1709 |
with vuetify3.VCol():
|
| 1710 |
-
vuetify3.
|
|
|
|
|
|
|
|
|
|
| 1711 |
|
| 1712 |
# Initial Distribution card
|
| 1713 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_distribution_card_style", _WORKFLOW_BASE_STYLE)):
|
|
@@ -1715,13 +2136,17 @@ def _build_control_panels(plotter):
|
|
| 1715 |
with vuetify3.VCardText():
|
| 1716 |
with vuetify3.VRow(classes="d-flex align-center mb-2", no_gutters=True):
|
| 1717 |
with vuetify3.VCol(cols="auto", classes="flex-grow-1"):
|
| 1718 |
-
vuetify3.
|
| 1719 |
-
|
| 1720 |
-
|
| 1721 |
-
|
| 1722 |
-
|
| 1723 |
-
|
| 1724 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1725 |
with vuetify3.VCol(cols="auto", classes="ml-2"):
|
| 1726 |
with vuetify3.VBtn(
|
| 1727 |
icon=True, density="compact", variant="text",
|
|
@@ -1734,30 +2159,42 @@ def _build_control_panels(plotter):
|
|
| 1734 |
vuetify3.VCardTitle("Sinusoidal Frequencies")
|
| 1735 |
with vuetify3.VCardText():
|
| 1736 |
for axis in ['x', 'y', 'z']:
|
| 1737 |
-
vuetify3.
|
| 1738 |
-
|
| 1739 |
-
|
| 1740 |
-
|
| 1741 |
-
|
| 1742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1743 |
|
| 1744 |
# Gaussian controls
|
| 1745 |
with vuetify3.VCard(classes="mb-2", v_if="qlbm_custom_dist_params && qlbm_dist_type === 'Gaussian'"):
|
| 1746 |
vuetify3.VCardTitle("Gaussian Parameters")
|
| 1747 |
with vuetify3.VCardText():
|
| 1748 |
for axis in ['x', 'y', 'z']:
|
| 1749 |
-
vuetify3.
|
| 1750 |
-
|
| 1751 |
-
|
| 1752 |
-
|
| 1753 |
-
|
| 1754 |
-
|
| 1755 |
-
|
| 1756 |
-
|
| 1757 |
-
|
| 1758 |
-
|
| 1759 |
-
|
| 1760 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1761 |
|
| 1762 |
# Multi-Dirac-Delta controls
|
| 1763 |
with vuetify3.VCard(classes="mb-2", v_if="qlbm_custom_dist_params && qlbm_dist_type === 'Multi-Dirac-Delta'"):
|
|
@@ -1765,19 +2202,26 @@ def _build_control_panels(plotter):
|
|
| 1765 |
with vuetify3.VCardText():
|
| 1766 |
vuetify3.VCardSubtitle("Number of delta peaks per axis = 2^k", classes="text-caption mb-2")
|
| 1767 |
for axis in ['x', 'y', 'z']:
|
| 1768 |
-
vuetify3.
|
| 1769 |
-
|
| 1770 |
-
|
| 1771 |
-
|
| 1772 |
-
|
| 1773 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1774 |
|
| 1775 |
# Boundary Conditions
|
| 1776 |
with vuetify3.VCard(classes="mb-2"):
|
| 1777 |
vuetify3.VCardTitle("Boundary Conditions", classes="text-subtitle-2 font-weight-bold text-primary")
|
| 1778 |
with vuetify3.VCardText():
|
| 1779 |
-
vuetify3.
|
| 1780 |
-
|
|
|
|
|
|
|
|
|
|
| 1781 |
|
| 1782 |
# Advecting Fields
|
| 1783 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_advect_card_style", _WORKFLOW_BASE_STYLE)):
|
|
@@ -1785,15 +2229,19 @@ def _build_control_panels(plotter):
|
|
| 1785 |
with vuetify3.VCardText():
|
| 1786 |
with vuetify3.VRow(classes="d-flex align-center mb-2", no_gutters=True):
|
| 1787 |
with vuetify3.VCol(cols="auto", classes="flex-grow-1"):
|
| 1788 |
-
vuetify3.
|
| 1789 |
-
|
| 1790 |
-
|
| 1791 |
-
|
| 1792 |
-
|
| 1793 |
-
|
| 1794 |
-
|
| 1795 |
-
|
| 1796 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1797 |
with vuetify3.VCol(cols="auto", classes="ml-2"):
|
| 1798 |
with vuetify3.VBtn(
|
| 1799 |
icon=True, density="compact", variant="text",
|
|
@@ -1802,9 +2250,18 @@ def _build_control_panels(plotter):
|
|
| 1802 |
vuetify3.VIcon("mdi-cog", color=("qlbm_show_advect_params ? 'primary' : 'grey'",))
|
| 1803 |
with vuetify3.VContainer(v_if="qlbm_show_advect_params", classes="pa-0 mt-2"):
|
| 1804 |
html.Div("Velocity Components", classes="text-caption mb-1")
|
| 1805 |
-
vuetify3.
|
| 1806 |
-
|
| 1807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1808 |
|
| 1809 |
# Meshing
|
| 1810 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_meshing_card_style", _WORKFLOW_BASE_STYLE)):
|
|
@@ -1837,8 +2294,11 @@ def _build_control_panels(plotter):
|
|
| 1837 |
with vuetify3.VCard(classes="mb-2"):
|
| 1838 |
vuetify3.VCardTitle("Time", classes="text-subtitle-2 font-weight-bold text-primary")
|
| 1839 |
with vuetify3.VCardText():
|
| 1840 |
-
vuetify3.
|
| 1841 |
-
|
|
|
|
|
|
|
|
|
|
| 1842 |
vuetify3.VAlert(v_if="qlbm_time_steps > 100", type="warning", variant="tonal", density="compact",
|
| 1843 |
children=["Warning: High time steps may increase runtime."], classes="mt-2")
|
| 1844 |
|
|
@@ -1886,13 +2346,33 @@ def _build_control_panels(plotter):
|
|
| 1886 |
children=["⚠️ Grid size > 16 may exceed IBM QPU capacity!"],
|
| 1887 |
classes="mt-2"
|
| 1888 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1889 |
|
| 1890 |
vuetify3.VDivider(classes="my-3")
|
| 1891 |
vuetify3.VBtn(
|
| 1892 |
text="Run",
|
| 1893 |
color="primary",
|
| 1894 |
block=True,
|
| 1895 |
-
disabled=("qlbm_is_running || !qlbm_backend_type", True),
|
| 1896 |
click=run_simulation,
|
| 1897 |
style=("qlbm_is_running ? '' : 'background-color:#87CEFA;'", ""),
|
| 1898 |
)
|
|
@@ -1925,6 +2405,108 @@ def _build_control_panels(plotter):
|
|
| 1925 |
click=stop_simulation,
|
| 1926 |
disabled=("!qlbm_is_running", True),
|
| 1927 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1928 |
|
| 1929 |
|
| 1930 |
def _build_visualization_panel(plotter):
|
|
|
|
| 12 |
import pyvista as pv
|
| 13 |
import plotly.graph_objects as go
|
| 14 |
import tempfile
|
| 15 |
+
import base64
|
| 16 |
+
import json
|
| 17 |
+
import asyncio
|
| 18 |
+
import threading
|
| 19 |
+
import time as time_module
|
| 20 |
from pathlib import Path
|
| 21 |
from datetime import datetime
|
| 22 |
from trame_vuetify.widgets import vuetify3
|
|
|
|
| 30 |
# --- Qiskit Backend Detection ---
|
| 31 |
_QISKIT_BACKEND_AVAILABLE = False
|
| 32 |
_QISKIT_IMPORT_ERROR = None
|
| 33 |
+
_VISUALIZE_COUNTS_AVAILABLE = False
|
| 34 |
|
| 35 |
try:
|
| 36 |
from qlbm.qlbm_sample_app import (
|
|
|
|
| 47 |
_QISKIT_IMPORT_ERROR = str(e)
|
| 48 |
print(f"Qiskit backend not available: {e}")
|
| 49 |
|
| 50 |
+
# Import visualize_counts for job result processing
|
| 51 |
+
try:
|
| 52 |
+
from qlbm.visualize_counts import (
|
| 53 |
+
load_samples,
|
| 54 |
+
estimate_density,
|
| 55 |
+
plot_density_isosurface_slider,
|
| 56 |
+
)
|
| 57 |
+
_VISUALIZE_COUNTS_AVAILABLE = True
|
| 58 |
+
except ImportError as e:
|
| 59 |
+
print(f"visualize_counts not available: {e}")
|
| 60 |
+
load_samples = None
|
| 61 |
+
estimate_density = None
|
| 62 |
+
plot_density_isosurface_slider = None
|
| 63 |
+
|
| 64 |
# --- CUDA-Q Backend Detection ---
|
| 65 |
def _env_flag(name: str) -> bool:
|
| 66 |
return os.environ.get(name, "").strip().lower() in ("1", "true", "yes")
|
|
|
|
| 110 |
simulation_times = []
|
| 111 |
current_grid_object = None
|
| 112 |
|
| 113 |
+
# --- Async infrastructure for real-time progress updates ---
|
| 114 |
+
_qlbm_main_loop = None # Reference to main event loop for thread-safe callbacks
|
| 115 |
+
_qlbm_heartbeat_thread = None
|
| 116 |
+
_qlbm_heartbeat_on = False
|
| 117 |
+
_qlbm_sim_start_time = None
|
| 118 |
+
|
| 119 |
+
def _qlbm_flush_state():
|
| 120 |
+
"""Force state flush to browser (synchronous, for main thread use)."""
|
| 121 |
+
try:
|
| 122 |
+
if _server:
|
| 123 |
+
_server.state.flush()
|
| 124 |
+
except Exception:
|
| 125 |
+
pass
|
| 126 |
+
|
| 127 |
+
def _qlbm_flush_state_threadsafe():
|
| 128 |
+
"""
|
| 129 |
+
Thread-safe state flush - schedules flush on the main event loop.
|
| 130 |
+
Use this from background threads (e.g., inside executor callbacks).
|
| 131 |
+
"""
|
| 132 |
+
global _qlbm_main_loop
|
| 133 |
+
try:
|
| 134 |
+
if _server and _qlbm_main_loop is not None and _qlbm_main_loop.is_running():
|
| 135 |
+
# Schedule the flush on the main event loop
|
| 136 |
+
_qlbm_main_loop.call_soon_threadsafe(_server.state.flush)
|
| 137 |
+
elif _server:
|
| 138 |
+
# Fallback: direct flush (may not work from threads)
|
| 139 |
+
_server.state.flush()
|
| 140 |
+
except Exception:
|
| 141 |
+
pass
|
| 142 |
+
|
| 143 |
+
async def _qlbm_flush_async():
|
| 144 |
+
"""Async helper to flush state and yield to event loop."""
|
| 145 |
+
_qlbm_flush_state()
|
| 146 |
+
await asyncio.sleep(0) # Yield control to event loop
|
| 147 |
+
|
| 148 |
+
def _qlbm_start_progress_heartbeat():
|
| 149 |
+
"""Start background thread for continuous progress updates."""
|
| 150 |
+
global _qlbm_heartbeat_thread, _qlbm_heartbeat_on, _qlbm_sim_start_time
|
| 151 |
+
|
| 152 |
+
if _qlbm_heartbeat_thread and _qlbm_heartbeat_thread.is_alive():
|
| 153 |
+
return
|
| 154 |
+
|
| 155 |
+
_qlbm_sim_start_time = time_module.time()
|
| 156 |
+
|
| 157 |
+
def loop_fn():
|
| 158 |
+
global _qlbm_heartbeat_on
|
| 159 |
+
while _qlbm_heartbeat_on:
|
| 160 |
+
if _state is not None and _state.qlbm_is_running and _qlbm_sim_start_time is not None:
|
| 161 |
+
elapsed = time_module.time() - _qlbm_sim_start_time
|
| 162 |
+
# Optionally update elapsed time state here if needed
|
| 163 |
+
_qlbm_flush_state_threadsafe()
|
| 164 |
+
time_module.sleep(0.1) # Update every 100ms
|
| 165 |
+
|
| 166 |
+
_qlbm_heartbeat_on = True
|
| 167 |
+
_qlbm_heartbeat_thread = threading.Thread(target=loop_fn, daemon=True)
|
| 168 |
+
_qlbm_heartbeat_thread.start()
|
| 169 |
+
|
| 170 |
+
def _qlbm_stop_progress_heartbeat():
|
| 171 |
+
"""Stop the background heartbeat thread."""
|
| 172 |
+
global _qlbm_heartbeat_on, _qlbm_heartbeat_thread
|
| 173 |
+
_qlbm_heartbeat_on = False
|
| 174 |
+
_qlbm_heartbeat_thread = None
|
| 175 |
+
|
| 176 |
GRID_SIZES = [8, 16, 32, 64, 128, 256]
|
| 177 |
_WORKFLOW_BASE_STYLE = "font-size: 0.8rem; border: 1px solid transparent; transition: box-shadow 0.2s ease;"
|
| 178 |
_WORKFLOW_HIGHLIGHT_STYLE = "font-size: 0.8rem; box-shadow: 0 0 0 2px #6200ea;"
|
|
|
|
| 296 |
"qlbm_qiskit_mode": False, # True when using Qiskit backend (shows Plotly slider)
|
| 297 |
"qlbm_qiskit_backend_available": _QISKIT_BACKEND_AVAILABLE,
|
| 298 |
"qlbm_qiskit_fig": None, # Stores the Plotly figure for Qiskit results
|
| 299 |
+
|
| 300 |
+
# Job upload state (for loading previously saved QPU job results)
|
| 301 |
+
"qlbm_job_upload": None, # File upload content
|
| 302 |
+
"qlbm_job_upload_filename": "", # Display the uploaded filename
|
| 303 |
+
"qlbm_job_upload_error": "", # Error message for upload
|
| 304 |
+
"qlbm_job_upload_success": "", # Success message for upload
|
| 305 |
+
"qlbm_job_platform": "IBM", # Platform: "IBM" or "IonQ"
|
| 306 |
+
"qlbm_job_total_time": 3, # Total time T (generates T_list = [1..T])
|
| 307 |
+
"qlbm_job_output_resolution": 40, # Grid resolution for density estimation
|
| 308 |
+
"qlbm_job_is_processing": False, # True when processing uploaded job
|
| 309 |
+
"qlbm_job_flag_qubits": True, # Whether flag qubits were used
|
| 310 |
+
"qlbm_job_midcircuit_meas": True, # Whether mid-circuit measurement was used (IBM only)
|
| 311 |
})
|
| 312 |
_initialized = True
|
| 313 |
|
|
|
|
| 1172 |
return output, fig, params["T_list"]
|
| 1173 |
|
| 1174 |
|
| 1175 |
+
# --- Job Result Upload Processing ---
|
| 1176 |
+
def process_uploaded_job_result():
|
| 1177 |
+
"""
|
| 1178 |
+
Process an uploaded IBM/IonQ job result JSON file and generate the Plotly figure.
|
| 1179 |
+
|
| 1180 |
+
This function:
|
| 1181 |
+
1. Decodes the uploaded file (base64 -> JSON)
|
| 1182 |
+
2. Parses the job result based on platform (IBM uses RuntimeDecoder, IonQ uses plain dict)
|
| 1183 |
+
3. Calls load_samples/estimate_density for each timestep
|
| 1184 |
+
4. Generates the slider figure using plot_density_isosurface_slider
|
| 1185 |
+
"""
|
| 1186 |
+
global simulation_data_frames, simulation_times, current_grid_object
|
| 1187 |
+
|
| 1188 |
+
if _state is None:
|
| 1189 |
+
return
|
| 1190 |
+
|
| 1191 |
+
# Validate required imports
|
| 1192 |
+
if not _VISUALIZE_COUNTS_AVAILABLE:
|
| 1193 |
+
_state.qlbm_job_upload_error = "visualize_counts module not available. Cannot process job results."
|
| 1194 |
+
log_to_console("Error: visualize_counts module not available")
|
| 1195 |
+
return
|
| 1196 |
+
|
| 1197 |
+
# Validate file upload
|
| 1198 |
+
uploaded = _state.qlbm_job_upload
|
| 1199 |
+
if not uploaded:
|
| 1200 |
+
_state.qlbm_job_upload_error = "No file uploaded. Please select a JSON file."
|
| 1201 |
+
return
|
| 1202 |
+
|
| 1203 |
+
# Reset messages
|
| 1204 |
+
_state.qlbm_job_upload_error = ""
|
| 1205 |
+
_state.qlbm_job_upload_success = ""
|
| 1206 |
+
_state.qlbm_job_is_processing = True
|
| 1207 |
+
log_to_console("Processing uploaded job result...")
|
| 1208 |
+
|
| 1209 |
+
try:
|
| 1210 |
+
# Handle list or single file
|
| 1211 |
+
file_data = uploaded[0] if isinstance(uploaded, list) else uploaded
|
| 1212 |
+
|
| 1213 |
+
# Get filename for display
|
| 1214 |
+
filename = file_data.get("name", "unknown.json") if isinstance(file_data, dict) else "unknown.json"
|
| 1215 |
+
_state.qlbm_job_upload_filename = filename
|
| 1216 |
+
log_to_console(f"Processing file: {filename}")
|
| 1217 |
+
|
| 1218 |
+
# Decode base64 content
|
| 1219 |
+
content = file_data.get("content", "") if isinstance(file_data, dict) else ""
|
| 1220 |
+
if content.startswith("data:"):
|
| 1221 |
+
content = content.split(",", 1)[1] # Remove data URI prefix
|
| 1222 |
+
|
| 1223 |
+
raw_bytes = base64.b64decode(content)
|
| 1224 |
+
json_str = raw_bytes.decode("utf-8")
|
| 1225 |
+
|
| 1226 |
+
# Parse timesteps from user input
|
| 1227 |
+
# User provides Total Time T, we generate T_list = [1, 2, ..., T]
|
| 1228 |
+
try:
|
| 1229 |
+
total_time = int(_state.qlbm_job_total_time or 3)
|
| 1230 |
+
if total_time < 1:
|
| 1231 |
+
total_time = 1
|
| 1232 |
+
T_list = list(range(1, total_time + 1))
|
| 1233 |
+
except ValueError:
|
| 1234 |
+
_state.qlbm_job_upload_error = "Invalid Total Time. Please enter a positive integer."
|
| 1235 |
+
_state.qlbm_job_is_processing = False
|
| 1236 |
+
return
|
| 1237 |
+
|
| 1238 |
+
log_to_console(f"Timesteps to process: {T_list}")
|
| 1239 |
+
|
| 1240 |
+
# Get processing parameters
|
| 1241 |
+
platform = _state.qlbm_job_platform or "IBM"
|
| 1242 |
+
output_resolution = int(_state.qlbm_job_output_resolution or 40)
|
| 1243 |
+
# Defaulting to True as per user request, and hiding from UI
|
| 1244 |
+
flag_qubits = True
|
| 1245 |
+
midcircuit_meas = True
|
| 1246 |
+
|
| 1247 |
+
log_to_console(f"Platform: {platform}, Resolution: {output_resolution}, Flag qubits: {flag_qubits}")
|
| 1248 |
+
|
| 1249 |
+
# Parse JSON based on platform
|
| 1250 |
+
if platform == "IBM":
|
| 1251 |
+
# IBM uses RuntimeDecoder for proper result parsing
|
| 1252 |
+
try:
|
| 1253 |
+
from qiskit_ibm_runtime import RuntimeDecoder
|
| 1254 |
+
result = json.loads(json_str, cls=RuntimeDecoder)
|
| 1255 |
+
log_to_console("Parsed IBM result with RuntimeDecoder")
|
| 1256 |
+
except ImportError:
|
| 1257 |
+
# Fallback to plain JSON if RuntimeDecoder not available
|
| 1258 |
+
result = json.loads(json_str)
|
| 1259 |
+
log_to_console("Warning: RuntimeDecoder not available, using plain JSON")
|
| 1260 |
+
except Exception as e:
|
| 1261 |
+
# Try plain JSON as fallback
|
| 1262 |
+
result = json.loads(json_str)
|
| 1263 |
+
log_to_console(f"RuntimeDecoder failed ({e}), using plain JSON")
|
| 1264 |
+
else:
|
| 1265 |
+
# IonQ uses plain JSON
|
| 1266 |
+
result = json.loads(json_str)
|
| 1267 |
+
log_to_console("Parsed IonQ result as plain JSON")
|
| 1268 |
+
|
| 1269 |
+
# Process each timestep
|
| 1270 |
+
output = []
|
| 1271 |
+
|
| 1272 |
+
# Determine how to extract counts based on result structure
|
| 1273 |
+
if platform == "IBM":
|
| 1274 |
+
# IBM PrimitiveResult structure: result is a list of PubResults
|
| 1275 |
+
# Each PubResult has .join_data().get_counts()
|
| 1276 |
+
if hasattr(result, '__iter__') and not isinstance(result, dict):
|
| 1277 |
+
for i, (T_total, pub) in enumerate(zip(T_list, result)):
|
| 1278 |
+
try:
|
| 1279 |
+
# Try the PrimitiveResult API
|
| 1280 |
+
if hasattr(pub, 'join_data'):
|
| 1281 |
+
joined = pub.join_data()
|
| 1282 |
+
counts = joined.get_counts()
|
| 1283 |
+
elif hasattr(pub, 'data'):
|
| 1284 |
+
# Older API
|
| 1285 |
+
counts = pub.data.get_counts() if hasattr(pub.data, 'get_counts') else dict(pub.data)
|
| 1286 |
+
elif isinstance(pub, dict):
|
| 1287 |
+
counts = pub
|
| 1288 |
+
else:
|
| 1289 |
+
counts = dict(pub)
|
| 1290 |
+
|
| 1291 |
+
log_to_console(f"Processing timestep T={T_total}: {len(counts)} unique bitstrings")
|
| 1292 |
+
pts, cnts = load_samples(counts, T_total, logger=log_to_console,
|
| 1293 |
+
flag_qubits=flag_qubits, midcircuit_meas=midcircuit_meas)
|
| 1294 |
+
output.append(estimate_density(pts, cnts, bandwidth=0.05, grid_size=output_resolution))
|
| 1295 |
+
except Exception as e:
|
| 1296 |
+
log_to_console(f"Error processing timestep {i}: {e}")
|
| 1297 |
+
elif isinstance(result, dict):
|
| 1298 |
+
# Single result or counts dict directly
|
| 1299 |
+
if 'counts' in result:
|
| 1300 |
+
counts = result['counts']
|
| 1301 |
+
else:
|
| 1302 |
+
counts = result
|
| 1303 |
+
for T_total in T_list:
|
| 1304 |
+
log_to_console(f"Processing timestep T={T_total}")
|
| 1305 |
+
pts, cnts = load_samples(counts, T_total, logger=log_to_console,
|
| 1306 |
+
flag_qubits=flag_qubits, midcircuit_meas=midcircuit_meas)
|
| 1307 |
+
output.append(estimate_density(pts, cnts, bandwidth=0.05, grid_size=output_resolution))
|
| 1308 |
+
else:
|
| 1309 |
+
# IonQ result structure: use get_counts(i) or direct counts dict
|
| 1310 |
+
if hasattr(result, 'get_counts'):
|
| 1311 |
+
for i, T_total in enumerate(T_list):
|
| 1312 |
+
try:
|
| 1313 |
+
counts = result.get_counts(i)
|
| 1314 |
+
log_to_console(f"Processing timestep T={T_total}: {len(counts)} unique bitstrings")
|
| 1315 |
+
pts, cnts = load_samples(counts, T_total, logger=log_to_console,
|
| 1316 |
+
flag_qubits=flag_qubits, midcircuit_meas=False)
|
| 1317 |
+
output.append(estimate_density(pts, cnts, bandwidth=0.05, grid_size=output_resolution))
|
| 1318 |
+
except Exception as e:
|
| 1319 |
+
log_to_console(f"Error processing timestep {i}: {e}")
|
| 1320 |
+
elif isinstance(result, list):
|
| 1321 |
+
# List of counts dicts
|
| 1322 |
+
for i, (T_total, counts) in enumerate(zip(T_list, result)):
|
| 1323 |
+
if isinstance(counts, dict):
|
| 1324 |
+
log_to_console(f"Processing timestep T={T_total}")
|
| 1325 |
+
pts, cnts = load_samples(counts, T_total, logger=log_to_console,
|
| 1326 |
+
flag_qubits=flag_qubits, midcircuit_meas=False)
|
| 1327 |
+
output.append(estimate_density(pts, cnts, bandwidth=0.05, grid_size=output_resolution))
|
| 1328 |
+
elif isinstance(result, dict):
|
| 1329 |
+
# Single counts dict
|
| 1330 |
+
counts = result.get('counts', result)
|
| 1331 |
+
for T_total in T_list:
|
| 1332 |
+
log_to_console(f"Processing timestep T={T_total}")
|
| 1333 |
+
pts, cnts = load_samples(counts, T_total, logger=log_to_console,
|
| 1334 |
+
flag_qubits=flag_qubits, midcircuit_meas=False)
|
| 1335 |
+
output.append(estimate_density(pts, cnts, bandwidth=0.05, grid_size=output_resolution))
|
| 1336 |
+
|
| 1337 |
+
if not output:
|
| 1338 |
+
_state.qlbm_job_upload_error = "No valid data extracted from job result. Check timesteps and file format."
|
| 1339 |
+
_state.qlbm_job_is_processing = False
|
| 1340 |
+
return
|
| 1341 |
+
|
| 1342 |
+
log_to_console(f"Processed {len(output)} timestep(s) successfully")
|
| 1343 |
+
|
| 1344 |
+
# Generate the Plotly figure
|
| 1345 |
+
fig = plot_density_isosurface_slider(output, T_list)
|
| 1346 |
+
|
| 1347 |
+
# Update state to show results
|
| 1348 |
+
_state.qlbm_qiskit_mode = True
|
| 1349 |
+
_state.qlbm_qiskit_fig = fig
|
| 1350 |
+
_state.qlbm_simulation_has_run = True
|
| 1351 |
+
_state.qlbm_job_upload_success = f"✓ Successfully processed {len(output)} timestep(s) from {filename}"
|
| 1352 |
+
|
| 1353 |
+
# Update the Plotly figure widget
|
| 1354 |
+
if hasattr(_ctrl, "qlbm_qiskit_plot_update"):
|
| 1355 |
+
_ctrl.qlbm_qiskit_plot_update(fig)
|
| 1356 |
+
|
| 1357 |
+
log_to_console(f"Results ready! {len(output)} frames generated.")
|
| 1358 |
+
|
| 1359 |
+
except json.JSONDecodeError as e:
|
| 1360 |
+
_state.qlbm_job_upload_error = f"Invalid JSON file: {e}"
|
| 1361 |
+
log_to_console(f"JSON decode error: {e}")
|
| 1362 |
+
except Exception as e:
|
| 1363 |
+
_state.qlbm_job_upload_error = f"Error processing job result: {e}"
|
| 1364 |
+
log_to_console(f"Processing error: {e}")
|
| 1365 |
+
import traceback
|
| 1366 |
+
log_to_console(traceback.format_exc())
|
| 1367 |
+
finally:
|
| 1368 |
+
_state.qlbm_job_is_processing = False
|
| 1369 |
+
|
| 1370 |
+
|
| 1371 |
# --- Main Simulation ---
|
| 1372 |
def run_simulation():
|
| 1373 |
+
"""
|
| 1374 |
+
Entry point for simulation - launches the async worker.
|
| 1375 |
+
This is called by the UI button click and schedules the async task.
|
| 1376 |
+
"""
|
| 1377 |
+
if _server is None:
|
| 1378 |
+
log_to_console("Error: Server not available")
|
| 1379 |
+
return
|
| 1380 |
+
|
| 1381 |
+
# Schedule the async simulation
|
| 1382 |
+
asyncio.ensure_future(_run_simulation_async())
|
| 1383 |
+
|
| 1384 |
+
|
| 1385 |
+
async def _run_simulation_async():
|
| 1386 |
+
"""
|
| 1387 |
+
Async simulation runner that uses thread pool for blocking work.
|
| 1388 |
+
This allows the UI to update in real-time during simulation.
|
| 1389 |
+
"""
|
| 1390 |
+
global simulation_data_frames, simulation_times, current_grid_object, _plotter, _qlbm_main_loop
|
| 1391 |
+
|
| 1392 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 1393 |
+
|
| 1394 |
+
# Capture the main event loop for thread-safe callbacks
|
| 1395 |
+
_qlbm_main_loop = asyncio.get_event_loop()
|
| 1396 |
+
|
| 1397 |
+
# Create executor for blocking operations
|
| 1398 |
+
executor = ThreadPoolExecutor(max_workers=1)
|
| 1399 |
+
loop = _qlbm_main_loop
|
| 1400 |
|
| 1401 |
if not _SIMULATION_CAN_RUN:
|
| 1402 |
msg = _SIMULATION_DISABLED_REASON or "Simulation backend is not available on this platform."
|
|
|
|
| 1404 |
log_to_console(f"Error: {msg}")
|
| 1405 |
_state.qlbm_status_message = "Error: Backend unavailable"
|
| 1406 |
_state.qlbm_status_type = "error"
|
| 1407 |
+
await _qlbm_flush_async()
|
| 1408 |
+
executor.shutdown(wait=False)
|
| 1409 |
return
|
| 1410 |
|
| 1411 |
_state.qlbm_is_running = True
|
|
|
|
| 1414 |
_state.qlbm_qiskit_mode = False # Reset Qiskit mode
|
| 1415 |
_state.qlbm_show_progress = True
|
| 1416 |
_state.qlbm_simulation_progress = 0
|
| 1417 |
+
_state.qlbm_status_message = "Initializing simulation..."
|
| 1418 |
_state.qlbm_status_type = "info"
|
| 1419 |
+
await _qlbm_flush_async()
|
| 1420 |
+
|
| 1421 |
+
# Start heartbeat for continuous progress updates
|
| 1422 |
+
_qlbm_start_progress_heartbeat()
|
| 1423 |
|
| 1424 |
# Determine if using Qiskit backend
|
| 1425 |
use_qiskit = (
|
|
|
|
| 1448 |
log_to_console("Job Initiated")
|
| 1449 |
log_to_console(f"Grid Size: {_state.qlbm_grid_size}×{_state.qlbm_grid_size}×{_state.qlbm_grid_size}, Time Steps: {_state.qlbm_time_steps}, Distribution: {_state.qlbm_dist_type}, Boundary: {_state.qlbm_boundary_condition}, Backend: {backend_info}, Velocity: vx={_state.qlbm_vx_expr}, vy={_state.qlbm_vy_expr}, vz={_state.qlbm_vz_expr}")
|
| 1450 |
|
| 1451 |
+
# Progress callback that uses thread-safe flush for real-time updates
|
| 1452 |
+
last_logged_percent = [0] # Use list for nonlocal in nested function
|
| 1453 |
def _progress_callback(percent):
|
|
|
|
| 1454 |
_state.qlbm_simulation_progress = percent
|
| 1455 |
+
if percent - last_logged_percent[0] >= 10:
|
| 1456 |
log_to_console(f"Simulation progress: {int(percent)}%")
|
| 1457 |
+
last_logged_percent[0] = percent
|
| 1458 |
+
_qlbm_flush_state_threadsafe() # Thread-safe flush!
|
| 1459 |
|
| 1460 |
try:
|
| 1461 |
# === Qiskit Backend (IBM Qiskit Simulator) ===
|
| 1462 |
if use_qiskit:
|
| 1463 |
log_to_console("Using IBM Qiskit Simulator backend...")
|
| 1464 |
+
_state.qlbm_status_message = "Running Qiskit Aer simulation..."
|
| 1465 |
+
await _qlbm_flush_async()
|
| 1466 |
+
|
| 1467 |
+
# Run Qiskit simulation in executor to keep UI responsive
|
| 1468 |
+
def _run_qiskit_blocking():
|
| 1469 |
+
return _run_qiskit_simulation(progress_callback=_progress_callback)
|
| 1470 |
|
| 1471 |
+
output, plotly_fig, T_list = await loop.run_in_executor(executor, _run_qiskit_blocking)
|
|
|
|
| 1472 |
|
| 1473 |
# Store results
|
| 1474 |
simulation_data_frames = output
|
|
|
|
| 1484 |
_state.qlbm_simulation_has_run = True
|
| 1485 |
_state.qlbm_qiskit_mode = True # Use Plotly display instead of PyVista
|
| 1486 |
|
| 1487 |
+
_state.qlbm_simulation_progress = 100
|
| 1488 |
log_to_console("Qiskit simulation completed successfully.")
|
| 1489 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1490 |
_state.qlbm_status_type = "success"
|
| 1491 |
+
await _qlbm_flush_async()
|
| 1492 |
|
| 1493 |
# === IBM QPU Backend ===
|
| 1494 |
elif use_ibm_qpu:
|
| 1495 |
log_to_console("Using IBM QPU backend...")
|
| 1496 |
+
_state.qlbm_status_message = "Preparing IBM QPU job..."
|
| 1497 |
+
await _qlbm_flush_async()
|
| 1498 |
|
| 1499 |
params = _map_state_to_qiskit_params()
|
| 1500 |
if params is None:
|
|
|
|
| 1502 |
|
| 1503 |
# Create initial state circuit
|
| 1504 |
log_to_console("Creating initial state circuit...")
|
| 1505 |
+
_state.qlbm_simulation_progress = 10
|
| 1506 |
+
await _qlbm_flush_async()
|
| 1507 |
+
|
| 1508 |
init_state_prep_circ = get_named_init_state_circuit(
|
| 1509 |
n=params["n"],
|
| 1510 |
init_state_name=params["init_state_name"],
|
|
|
|
| 1521 |
)
|
| 1522 |
|
| 1523 |
log_to_console("Submitting job to IBM Quantum...")
|
| 1524 |
+
_state.qlbm_status_message = "Submitting job to IBM Quantum..."
|
| 1525 |
+
_state.qlbm_simulation_progress = 20
|
| 1526 |
+
await _qlbm_flush_async()
|
| 1527 |
|
| 1528 |
+
# Run HW simulation in executor
|
| 1529 |
+
def _run_ibm_qpu_blocking():
|
| 1530 |
+
log_to_console("Submitting and running IBM QPU job...")
|
| 1531 |
+
job, get_result = run_sampling_hw_ibm(
|
| 1532 |
+
n=params["n"],
|
| 1533 |
+
ux=params["vx_expr"],
|
| 1534 |
+
uy=params["vy_expr"],
|
| 1535 |
+
uz=params["vz_expr"],
|
| 1536 |
+
init_state_prep_circ=init_state_prep_circ,
|
| 1537 |
+
T_list=params["T_list"],
|
| 1538 |
+
shots=2**14,
|
| 1539 |
+
vel_resolution=min(params['grid_size'], 32),
|
| 1540 |
+
output_resolution=min(2*params['grid_size'], 40),
|
| 1541 |
+
logger=log_to_console
|
| 1542 |
+
)
|
| 1543 |
+
log_to_console("Waiting for job results (this may take time)...")
|
| 1544 |
+
output, plotly_fig = get_result(job)
|
| 1545 |
+
return output, plotly_fig, init_state_prep_circ
|
| 1546 |
|
| 1547 |
+
_state.qlbm_status_message = "Waiting for IBM QPU results..."
|
| 1548 |
+
_state.qlbm_simulation_progress = 40
|
| 1549 |
+
await _qlbm_flush_async()
|
| 1550 |
+
|
| 1551 |
+
output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ibm_qpu_blocking)
|
| 1552 |
+
|
| 1553 |
+
_state.qlbm_simulation_progress = 80
|
| 1554 |
+
await _qlbm_flush_async()
|
| 1555 |
|
| 1556 |
# Generate T=0 initial snapshot and prepend to output
|
| 1557 |
log_to_console("Generating T=0 initial distribution snapshot...")
|
|
|
|
| 1587 |
log_to_console(f"Warning: Could not generate T=0 snapshot: {exc}")
|
| 1588 |
extended_T_list = params["T_list"]
|
| 1589 |
|
| 1590 |
+
# Store results
|
| 1591 |
+
simulation_data_frames = output
|
| 1592 |
+
simulation_times = [float(t) for t in extended_T_list]
|
| 1593 |
+
|
| 1594 |
# Update UI
|
| 1595 |
if hasattr(_ctrl, "qlbm_qiskit_result_update"):
|
| 1596 |
_ctrl.qlbm_qiskit_result_update(plotly_fig)
|
|
|
|
| 1601 |
_state.qlbm_simulation_has_run = True
|
| 1602 |
_state.qlbm_qiskit_mode = True
|
| 1603 |
|
| 1604 |
+
_state.qlbm_simulation_progress = 100
|
| 1605 |
log_to_console("IBM QPU simulation completed successfully.")
|
| 1606 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1607 |
_state.qlbm_status_type = "success"
|
| 1608 |
+
await _qlbm_flush_async()
|
| 1609 |
|
| 1610 |
# === IonQ QPU Backend ===
|
| 1611 |
elif use_ionq_qpu:
|
| 1612 |
log_to_console("Using IonQ QPU backend...")
|
| 1613 |
+
_state.qlbm_status_message = "Preparing IonQ QPU job..."
|
| 1614 |
+
await _qlbm_flush_async()
|
| 1615 |
|
| 1616 |
params = _map_state_to_qiskit_params()
|
| 1617 |
if params is None:
|
|
|
|
| 1619 |
|
| 1620 |
# Create initial state circuit
|
| 1621 |
log_to_console("Creating initial state circuit...")
|
| 1622 |
+
_state.qlbm_simulation_progress = 10
|
| 1623 |
+
await _qlbm_flush_async()
|
| 1624 |
+
|
| 1625 |
init_state_prep_circ = get_named_init_state_circuit(
|
| 1626 |
n=params["n"],
|
| 1627 |
init_state_name=params["init_state_name"],
|
|
|
|
| 1638 |
)
|
| 1639 |
|
| 1640 |
log_to_console("Submitting job to IonQ Quantum...")
|
| 1641 |
+
_state.qlbm_status_message = "Submitting job to IonQ Quantum..."
|
| 1642 |
+
_state.qlbm_simulation_progress = 20
|
| 1643 |
+
await _qlbm_flush_async()
|
| 1644 |
|
| 1645 |
+
# Run IonQ HW simulation in executor
|
| 1646 |
+
def _run_ionq_qpu_blocking():
|
| 1647 |
+
log_to_console("Submitting and running IonQ QPU job...")
|
| 1648 |
+
job, get_result = run_sampling_hw_ionq(
|
| 1649 |
+
n=params["n"],
|
| 1650 |
+
ux=params["vx_expr"],
|
| 1651 |
+
uy=params["vy_expr"],
|
| 1652 |
+
uz=params["vz_expr"],
|
| 1653 |
+
init_state_prep_circ=init_state_prep_circ,
|
| 1654 |
+
T_list=params["T_list"],
|
| 1655 |
+
shots=2**14,
|
| 1656 |
+
vel_resolution=min(params['grid_size'], 32),
|
| 1657 |
+
output_resolution=min(2*params['grid_size'], 40),
|
| 1658 |
+
logger=log_to_console
|
| 1659 |
+
)
|
| 1660 |
+
log_to_console("Waiting for IonQ job results (this may take time)...")
|
| 1661 |
+
output, plotly_fig = get_result(job)
|
| 1662 |
+
return output, plotly_fig, init_state_prep_circ
|
| 1663 |
+
|
| 1664 |
+
_state.qlbm_status_message = "Waiting for IonQ QPU results..."
|
| 1665 |
+
_state.qlbm_simulation_progress = 40
|
| 1666 |
+
await _qlbm_flush_async()
|
| 1667 |
|
| 1668 |
+
output, plotly_fig, init_state_prep_circ = await loop.run_in_executor(executor, _run_ionq_qpu_blocking)
|
| 1669 |
+
|
| 1670 |
+
_state.qlbm_simulation_progress = 80
|
| 1671 |
+
await _qlbm_flush_async()
|
| 1672 |
|
| 1673 |
# Generate T=0 initial snapshot and prepend to output
|
| 1674 |
log_to_console("Generating T=0 initial distribution snapshot...")
|
|
|
|
| 1704 |
log_to_console(f"Warning: Could not generate T=0 snapshot: {exc}")
|
| 1705 |
extended_T_list = params["T_list"]
|
| 1706 |
|
| 1707 |
+
# Store results
|
| 1708 |
+
simulation_data_frames = output
|
| 1709 |
+
simulation_times = [float(t) for t in extended_T_list]
|
| 1710 |
+
|
| 1711 |
# Update UI
|
| 1712 |
if hasattr(_ctrl, "qlbm_qiskit_result_update"):
|
| 1713 |
_ctrl.qlbm_qiskit_result_update(plotly_fig)
|
|
|
|
| 1718 |
_state.qlbm_simulation_has_run = True
|
| 1719 |
_state.qlbm_qiskit_mode = True
|
| 1720 |
|
| 1721 |
+
_state.qlbm_simulation_progress = 100
|
| 1722 |
log_to_console("IonQ QPU simulation completed successfully.")
|
| 1723 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1724 |
_state.qlbm_status_type = "success"
|
| 1725 |
+
await _qlbm_flush_async()
|
| 1726 |
|
| 1727 |
# === CUDA-Q Backend ===
|
| 1728 |
elif _state.qlbm_backend_type == "Simulator" and _state.qlbm_selected_simulator == "CUDA-Q simulator":
|
| 1729 |
_state.qlbm_qiskit_mode = False # Use PyVista display
|
| 1730 |
+
_state.qlbm_status_message = "Running CUDA-Q simulation..."
|
| 1731 |
+
await _qlbm_flush_async()
|
| 1732 |
|
| 1733 |
grid_size = int(_state.qlbm_grid_size)
|
| 1734 |
num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
|
|
|
|
| 1740 |
vy_func = make_velocity_func(_state.qlbm_vy_expr)
|
| 1741 |
vz_func = make_velocity_func(_state.qlbm_vz_expr)
|
| 1742 |
|
| 1743 |
+
_state.qlbm_simulation_progress = 5
|
| 1744 |
+
await _qlbm_flush_async()
|
| 1745 |
|
| 1746 |
if simulate_qlbm_3D_and_animate is not None:
|
| 1747 |
log_to_console("Running CUDA-Q Simulation...")
|
| 1748 |
+
|
| 1749 |
+
# Run CUDA-Q simulation in executor
|
| 1750 |
+
def _run_cudaq_blocking():
|
| 1751 |
+
_plotter.clear()
|
| 1752 |
+
return simulate_qlbm_3D_and_animate(
|
| 1753 |
+
num_reg_qubits=num_reg_qubits,
|
| 1754 |
+
T=T,
|
| 1755 |
+
distribution_type=distribution_type,
|
| 1756 |
+
vx_input=vx_func,
|
| 1757 |
+
vy_input=vy_func,
|
| 1758 |
+
vz_input=vz_func,
|
| 1759 |
+
boundary_condition=boundary_condition,
|
| 1760 |
+
plotter=_plotter,
|
| 1761 |
+
add_slider=False,
|
| 1762 |
+
progress_callback=_progress_callback
|
| 1763 |
+
)
|
| 1764 |
+
|
| 1765 |
+
result = await loop.run_in_executor(executor, _run_cudaq_blocking)
|
| 1766 |
+
_, frames, times, grid_obj = result
|
| 1767 |
else:
|
| 1768 |
# Fallback to CPU demo if CUDA-Q not available
|
| 1769 |
log_to_console("CUDA-Q not available, falling back to CPU Demo...")
|
| 1770 |
+
|
| 1771 |
+
def _run_cpu_demo_blocking():
|
| 1772 |
+
return _run_cpu_demo_simulation(
|
| 1773 |
+
grid_size=grid_size,
|
| 1774 |
+
T=T,
|
| 1775 |
+
distribution_type=distribution_type or "Sinusoidal",
|
| 1776 |
+
vx_func=vx_func,
|
| 1777 |
+
vy_func=vy_func,
|
| 1778 |
+
vz_func=vz_func,
|
| 1779 |
+
progress_callback=_progress_callback
|
| 1780 |
+
)
|
| 1781 |
+
|
| 1782 |
+
frames, times, grid_obj = await loop.run_in_executor(executor, _run_cpu_demo_blocking)
|
| 1783 |
|
| 1784 |
+
_state.qlbm_simulation_progress = 95
|
| 1785 |
+
await _qlbm_flush_async()
|
| 1786 |
|
| 1787 |
# Update plotter with results
|
| 1788 |
if grid_obj:
|
|
|
|
| 1811 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1812 |
_state.qlbm_status_type = "success"
|
| 1813 |
_state.qlbm_simulation_progress = 100
|
| 1814 |
+
await _qlbm_flush_async()
|
| 1815 |
else:
|
| 1816 |
_state.qlbm_run_error = "Simulation produced no data."
|
| 1817 |
log_to_console("Error: Simulation produced no data.")
|
| 1818 |
_state.qlbm_status_message = "Error: No data produced"
|
| 1819 |
_state.qlbm_status_type = "error"
|
| 1820 |
+
await _qlbm_flush_async()
|
| 1821 |
|
| 1822 |
# === CPU Demo Backend (for QPU or fallback) ===
|
| 1823 |
else:
|
| 1824 |
_state.qlbm_qiskit_mode = False # Use PyVista display
|
| 1825 |
+
_state.qlbm_status_message = "Running CPU Demo simulation..."
|
| 1826 |
+
await _qlbm_flush_async()
|
| 1827 |
|
| 1828 |
grid_size = int(_state.qlbm_grid_size)
|
| 1829 |
num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
|
|
|
|
| 1837 |
|
| 1838 |
_progress_callback(0)
|
| 1839 |
|
| 1840 |
+
_state.qlbm_simulation_progress = 5
|
| 1841 |
+
await _qlbm_flush_async()
|
| 1842 |
+
|
| 1843 |
+
# CPU Demo Simulation in executor
|
| 1844 |
log_to_console("Running CPU Demo Simulation...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1845 |
|
| 1846 |
+
def _run_cpu_demo_fallback():
|
| 1847 |
+
return _run_cpu_demo_simulation(
|
| 1848 |
+
grid_size=grid_size,
|
| 1849 |
+
T=T,
|
| 1850 |
+
distribution_type=distribution_type or "Sinusoidal",
|
| 1851 |
+
vx_func=vx_func,
|
| 1852 |
+
vy_func=vy_func,
|
| 1853 |
+
vz_func=vz_func,
|
| 1854 |
+
progress_callback=_progress_callback
|
| 1855 |
+
)
|
| 1856 |
+
|
| 1857 |
+
frames, times, grid_obj = await loop.run_in_executor(executor, _run_cpu_demo_fallback)
|
| 1858 |
+
|
| 1859 |
+
_state.qlbm_simulation_progress = 95
|
| 1860 |
+
await _qlbm_flush_async()
|
| 1861 |
|
| 1862 |
# Update plotter with results
|
| 1863 |
if grid_obj:
|
|
|
|
| 1886 |
_state.qlbm_status_message = "Simulation completed successfully."
|
| 1887 |
_state.qlbm_status_type = "success"
|
| 1888 |
_state.qlbm_simulation_progress = 100
|
| 1889 |
+
await _qlbm_flush_async()
|
| 1890 |
else:
|
| 1891 |
_state.qlbm_run_error = "Simulation produced no data."
|
| 1892 |
log_to_console("Error: Simulation produced no data.")
|
| 1893 |
_state.qlbm_status_message = "Error: No data produced"
|
| 1894 |
_state.qlbm_status_type = "error"
|
| 1895 |
+
await _qlbm_flush_async()
|
| 1896 |
|
| 1897 |
except Exception as e:
|
| 1898 |
_state.qlbm_run_error = f"Simulation failed: {str(e)}"
|
|
|
|
| 1902 |
traceback.print_exc()
|
| 1903 |
_state.qlbm_status_message = "Simulation failed"
|
| 1904 |
_state.qlbm_status_type = "error"
|
| 1905 |
+
await _qlbm_flush_async()
|
| 1906 |
finally:
|
| 1907 |
_state.qlbm_is_running = False
|
| 1908 |
+
_qlbm_stop_progress_heartbeat()
|
| 1909 |
+
executor.shutdown(wait=False)
|
| 1910 |
if _state.qlbm_status_type != "success":
|
| 1911 |
_state.qlbm_show_progress = False
|
| 1912 |
+
await _qlbm_flush_async()
|
| 1913 |
|
| 1914 |
|
| 1915 |
def stop_simulation():
|
|
|
|
| 2067 |
with vuetify3.VCardText():
|
| 2068 |
vuetify3.VDivider(classes="my-2")
|
| 2069 |
vuetify3.VCardSubtitle("Problems", classes="text-caption font-weight-bold mt-2")
|
| 2070 |
+
with vuetify3.VTooltip(location="top"):
|
| 2071 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2072 |
+
vuetify3.VSelect(
|
| 2073 |
+
v_bind="props",
|
| 2074 |
+
key="qlbm_overview_problems",
|
| 2075 |
+
label="Select a problem",
|
| 2076 |
+
v_model=("qlbm_problems_selection", None),
|
| 2077 |
+
items=(
|
| 2078 |
+
"qlbm_qlbm_problems",
|
| 2079 |
+
[
|
| 2080 |
+
"Scalar advection-diffusion in a box",
|
| 2081 |
+
"Laminar flow & heat transfer for a heated body in water.",
|
| 2082 |
+
],
|
| 2083 |
+
),
|
| 2084 |
+
placeholder="Select",
|
| 2085 |
+
density="compact",
|
| 2086 |
+
hide_details=True,
|
| 2087 |
+
color="primary",
|
| 2088 |
+
classes="mb-2"
|
| 2089 |
+
)
|
| 2090 |
+
html.Span("Select a predefined fluid dynamics problem to solve")
|
| 2091 |
|
| 2092 |
# Geometry card
|
| 2093 |
with vuetify3.VCard(classes="mb-2"):
|
|
|
|
| 2115 |
vuetify3.VCardSubtitle("Domain dimensions", classes="text-caption font-weight-bold mb-2")
|
| 2116 |
with vuetify3.VRow(dense=True):
|
| 2117 |
with vuetify3.VCol():
|
| 2118 |
+
with vuetify3.VTooltip(location="top"):
|
| 2119 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2120 |
+
vuetify3.VTextField(v_bind="props", label="Length (L)", v_model=("qlbm_domain_L", 1.0), type="number", step="0.1", density="compact", hide_details=True, color="primary")
|
| 2121 |
+
html.Span("Length of the domain along X axis")
|
| 2122 |
with vuetify3.VCol():
|
| 2123 |
+
with vuetify3.VTooltip(location="top"):
|
| 2124 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2125 |
+
vuetify3.VTextField(v_bind="props", label="Width (W)", v_model=("qlbm_domain_W", 1.0), type="number", step="0.1", density="compact", hide_details=True, color="primary")
|
| 2126 |
+
html.Span("Width of the domain along Y axis")
|
| 2127 |
with vuetify3.VCol():
|
| 2128 |
+
with vuetify3.VTooltip(location="top"):
|
| 2129 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2130 |
+
vuetify3.VTextField(v_bind="props", label="Height (H)", v_model=("qlbm_domain_H", 1.0), type="number", step="0.1", density="compact", hide_details=True, color="primary")
|
| 2131 |
+
html.Span("Height of the domain along Z axis")
|
| 2132 |
|
| 2133 |
# Initial Distribution card
|
| 2134 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_distribution_card_style", _WORKFLOW_BASE_STYLE)):
|
|
|
|
| 2136 |
with vuetify3.VCardText():
|
| 2137 |
with vuetify3.VRow(classes="d-flex align-center mb-2", no_gutters=True):
|
| 2138 |
with vuetify3.VCol(cols="auto", classes="flex-grow-1"):
|
| 2139 |
+
with vuetify3.VTooltip(location="top"):
|
| 2140 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2141 |
+
vuetify3.VSelect(
|
| 2142 |
+
v_bind="props",
|
| 2143 |
+
label="Initial Distribution",
|
| 2144 |
+
v_model=("qlbm_dist_type", None),
|
| 2145 |
+
items=("qlbm_dist_modes",),
|
| 2146 |
+
density="compact",
|
| 2147 |
+
hide_details=True
|
| 2148 |
+
)
|
| 2149 |
+
html.Span("Select the initial density distribution function")
|
| 2150 |
with vuetify3.VCol(cols="auto", classes="ml-2"):
|
| 2151 |
with vuetify3.VBtn(
|
| 2152 |
icon=True, density="compact", variant="text",
|
|
|
|
| 2159 |
vuetify3.VCardTitle("Sinusoidal Frequencies")
|
| 2160 |
with vuetify3.VCardText():
|
| 2161 |
for axis in ['x', 'y', 'z']:
|
| 2162 |
+
with vuetify3.VTooltip(location="top"):
|
| 2163 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2164 |
+
vuetify3.VSlider(
|
| 2165 |
+
v_bind="props",
|
| 2166 |
+
label=f"Freq {axis.upper()}",
|
| 2167 |
+
v_model=(f"qlbm_sine_k_{axis}", 1.0),
|
| 2168 |
+
min=1, max=5, step=1,
|
| 2169 |
+
thumb_label="always", density="compact"
|
| 2170 |
+
)
|
| 2171 |
+
html.Span(f"Frequency multiplier for {axis.upper()} axis")
|
| 2172 |
|
| 2173 |
# Gaussian controls
|
| 2174 |
with vuetify3.VCard(classes="mb-2", v_if="qlbm_custom_dist_params && qlbm_dist_type === 'Gaussian'"):
|
| 2175 |
vuetify3.VCardTitle("Gaussian Parameters")
|
| 2176 |
with vuetify3.VCardText():
|
| 2177 |
for axis in ['x', 'y', 'z']:
|
| 2178 |
+
with vuetify3.VTooltip(location="top"):
|
| 2179 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2180 |
+
vuetify3.VSlider(
|
| 2181 |
+
v_bind="props",
|
| 2182 |
+
label=f"Center {axis.upper()}",
|
| 2183 |
+
v_model=(f"qlbm_gauss_c{axis}", 16),
|
| 2184 |
+
min=0, max=("qlbm_nx", 32), step=1,
|
| 2185 |
+
thumb_label="always", density="compact"
|
| 2186 |
+
)
|
| 2187 |
+
html.Span(f"Center position along {axis.upper()} axis")
|
| 2188 |
+
with vuetify3.VTooltip(location="top"):
|
| 2189 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2190 |
+
vuetify3.VSlider(
|
| 2191 |
+
v_bind="props",
|
| 2192 |
+
label="Width (Sigma)",
|
| 2193 |
+
v_model=("qlbm_gauss_sigma", 6.0),
|
| 2194 |
+
min=1.0, max=20.0, step=0.5,
|
| 2195 |
+
thumb_label="always", density="compact"
|
| 2196 |
+
)
|
| 2197 |
+
html.Span("Standard deviation (spread) of the Gaussian")
|
| 2198 |
|
| 2199 |
# Multi-Dirac-Delta controls
|
| 2200 |
with vuetify3.VCard(classes="mb-2", v_if="qlbm_custom_dist_params && qlbm_dist_type === 'Multi-Dirac-Delta'"):
|
|
|
|
| 2202 |
with vuetify3.VCardText():
|
| 2203 |
vuetify3.VCardSubtitle("Number of delta peaks per axis = 2^k", classes="text-caption mb-2")
|
| 2204 |
for axis in ['x', 'y', 'z']:
|
| 2205 |
+
with vuetify3.VTooltip(location="top"):
|
| 2206 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2207 |
+
vuetify3.VSlider(
|
| 2208 |
+
v_bind="props",
|
| 2209 |
+
label=f"k_{axis.upper()} (log₂)",
|
| 2210 |
+
v_model=(f"qlbm_mdd_k{axis}_log2", 1),
|
| 2211 |
+
min=1, max=4, step=1,
|
| 2212 |
+
thumb_label="always", density="compact"
|
| 2213 |
+
)
|
| 2214 |
+
html.Span(f"Log2 of number of peaks along {axis.upper()}")
|
| 2215 |
|
| 2216 |
# Boundary Conditions
|
| 2217 |
with vuetify3.VCard(classes="mb-2"):
|
| 2218 |
vuetify3.VCardTitle("Boundary Conditions", classes="text-subtitle-2 font-weight-bold text-primary")
|
| 2219 |
with vuetify3.VCardText():
|
| 2220 |
+
with vuetify3.VTooltip(location="top"):
|
| 2221 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2222 |
+
vuetify3.VSelect(v_bind="props", label="Boundary Condition", v_model=("qlbm_boundary_condition", "Periodic"),
|
| 2223 |
+
items=("['Periodic']",), density="compact", hide_details=True, color="primary")
|
| 2224 |
+
html.Span("Select boundary conditions for the simulation domain")
|
| 2225 |
|
| 2226 |
# Advecting Fields
|
| 2227 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_advect_card_style", _WORKFLOW_BASE_STYLE)):
|
|
|
|
| 2229 |
with vuetify3.VCardText():
|
| 2230 |
with vuetify3.VRow(classes="d-flex align-center mb-2", no_gutters=True):
|
| 2231 |
with vuetify3.VCol(cols="auto", classes="flex-grow-1"):
|
| 2232 |
+
with vuetify3.VTooltip(location="top"):
|
| 2233 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2234 |
+
vuetify3.VSelect(
|
| 2235 |
+
v_bind="props",
|
| 2236 |
+
label="Select Advecting field",
|
| 2237 |
+
v_model=("qlbm_advecting_field", None),
|
| 2238 |
+
items=("['Uniform', 'Swirl', 'Shear', 'TGV']",),
|
| 2239 |
+
density="compact",
|
| 2240 |
+
hide_details=True,
|
| 2241 |
+
color="primary",
|
| 2242 |
+
placeholder="Select",
|
| 2243 |
+
)
|
| 2244 |
+
html.Span("Select the velocity field that transports the fluid")
|
| 2245 |
with vuetify3.VCol(cols="auto", classes="ml-2"):
|
| 2246 |
with vuetify3.VBtn(
|
| 2247 |
icon=True, density="compact", variant="text",
|
|
|
|
| 2250 |
vuetify3.VIcon("mdi-cog", color=("qlbm_show_advect_params ? 'primary' : 'grey'",))
|
| 2251 |
with vuetify3.VContainer(v_if="qlbm_show_advect_params", classes="pa-0 mt-2"):
|
| 2252 |
html.Div("Velocity Components", classes="text-caption mb-1")
|
| 2253 |
+
with vuetify3.VTooltip(location="top"):
|
| 2254 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2255 |
+
vuetify3.VTextField(v_bind="props", label="Velocity vx", v_model=("qlbm_vx_expr", "0.2"), density="compact", hide_details=True, color="primary", classes="mb-1")
|
| 2256 |
+
html.Span("X-component of velocity field")
|
| 2257 |
+
with vuetify3.VTooltip(location="top"):
|
| 2258 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2259 |
+
vuetify3.VTextField(v_bind="props", label="Velocity vy", v_model=("qlbm_vy_expr", "-0.15"), density="compact", hide_details=True, color="primary", classes="mb-1")
|
| 2260 |
+
html.Span("Y-component of velocity field")
|
| 2261 |
+
with vuetify3.VTooltip(location="top"):
|
| 2262 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2263 |
+
vuetify3.VTextField(v_bind="props", label="Velocity vz", v_model=("qlbm_vz_expr", "0.3"), density="compact", hide_details=True, color="primary")
|
| 2264 |
+
html.Span("Z-component of velocity field")
|
| 2265 |
|
| 2266 |
# Meshing
|
| 2267 |
with vuetify3.VCard(classes="mb-2", style=("qlbm_meshing_card_style", _WORKFLOW_BASE_STYLE)):
|
|
|
|
| 2294 |
with vuetify3.VCard(classes="mb-2"):
|
| 2295 |
vuetify3.VCardTitle("Time", classes="text-subtitle-2 font-weight-bold text-primary")
|
| 2296 |
with vuetify3.VCardText():
|
| 2297 |
+
with vuetify3.VTooltip(location="top"):
|
| 2298 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2299 |
+
vuetify3.VSlider(v_bind="props", label="Total Time", v_model=("qlbm_time_steps", 10), min=0, max=30, step=1,
|
| 2300 |
+
thumb_label="always", show_ticks="always", color="primary", density="compact", hide_details=True)
|
| 2301 |
+
html.Span("Number of time steps to simulate")
|
| 2302 |
vuetify3.VAlert(v_if="qlbm_time_steps > 100", type="warning", variant="tonal", density="compact",
|
| 2303 |
children=["Warning: High time steps may increase runtime."], classes="mt-2")
|
| 2304 |
|
|
|
|
| 2346 |
children=["⚠️ Grid size > 16 may exceed IBM QPU capacity!"],
|
| 2347 |
classes="mt-2"
|
| 2348 |
)
|
| 2349 |
+
|
| 2350 |
+
# Sinusoidal Warning for IBM QPU
|
| 2351 |
+
vuetify3.VAlert(
|
| 2352 |
+
v_if="qlbm_backend_type === 'QPU' && qlbm_selected_qpu === 'IBM QPU' && qlbm_dist_type === 'Sinusoidal'",
|
| 2353 |
+
type="warning",
|
| 2354 |
+
variant="tonal",
|
| 2355 |
+
density="compact",
|
| 2356 |
+
children=["⚠️ Sinusoidal distribution results in very high circuit depth on IBM QPU!"],
|
| 2357 |
+
classes="mt-2"
|
| 2358 |
+
)
|
| 2359 |
+
|
| 2360 |
+
# IonQ Restriction Warning
|
| 2361 |
+
vuetify3.VAlert(
|
| 2362 |
+
v_if="qlbm_backend_type === 'QPU' && qlbm_selected_qpu === 'IonQ QPU' && qlbm_dist_type !== 'Multi-Dirac-Delta'",
|
| 2363 |
+
type="error",
|
| 2364 |
+
variant="tonal",
|
| 2365 |
+
density="compact",
|
| 2366 |
+
children=["⚠️ IonQ QPU only supports Multi-Dirac-Delta distribution."],
|
| 2367 |
+
classes="mt-2"
|
| 2368 |
+
)
|
| 2369 |
|
| 2370 |
vuetify3.VDivider(classes="my-3")
|
| 2371 |
vuetify3.VBtn(
|
| 2372 |
text="Run",
|
| 2373 |
color="primary",
|
| 2374 |
block=True,
|
| 2375 |
+
disabled=("qlbm_is_running || !qlbm_backend_type || (qlbm_backend_type === 'QPU' && qlbm_selected_qpu === 'IonQ QPU' && qlbm_dist_type !== 'Multi-Dirac-Delta')", True),
|
| 2376 |
click=run_simulation,
|
| 2377 |
style=("qlbm_is_running ? '' : 'background-color:#87CEFA;'", ""),
|
| 2378 |
)
|
|
|
|
| 2405 |
click=stop_simulation,
|
| 2406 |
disabled=("!qlbm_is_running", True),
|
| 2407 |
)
|
| 2408 |
+
|
| 2409 |
+
# --- Job Result Upload Section ---
|
| 2410 |
+
vuetify3.VDivider(classes="my-3")
|
| 2411 |
+
html.Div("Upload Job Results", classes="text-subtitle-2 font-weight-bold text-primary mb-2")
|
| 2412 |
+
html.Div("Load previously saved IBM/IonQ QPU job results (JSON format)",
|
| 2413 |
+
classes="text-caption text-medium-emphasis mb-2")
|
| 2414 |
+
|
| 2415 |
+
# Platform selector
|
| 2416 |
+
with vuetify3.VRow(dense=True, classes="mb-2"):
|
| 2417 |
+
with vuetify3.VCol(cols=6):
|
| 2418 |
+
with vuetify3.VTooltip(location="top"):
|
| 2419 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2420 |
+
vuetify3.VSelect(
|
| 2421 |
+
v_bind="props",
|
| 2422 |
+
label="Platform",
|
| 2423 |
+
v_model=("qlbm_job_platform", "IBM"),
|
| 2424 |
+
items=("['IBM', 'IonQ']",),
|
| 2425 |
+
density="compact",
|
| 2426 |
+
hide_details=True,
|
| 2427 |
+
color="primary",
|
| 2428 |
+
)
|
| 2429 |
+
html.Span("Select the quantum hardware provider")
|
| 2430 |
+
with vuetify3.VCol(cols=6):
|
| 2431 |
+
with vuetify3.VTooltip(location="top"):
|
| 2432 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2433 |
+
vuetify3.VTextField(
|
| 2434 |
+
v_bind="props",
|
| 2435 |
+
label="Output Resolution",
|
| 2436 |
+
v_model=("qlbm_job_output_resolution", 40),
|
| 2437 |
+
type="number",
|
| 2438 |
+
density="compact",
|
| 2439 |
+
hide_details=True,
|
| 2440 |
+
color="primary",
|
| 2441 |
+
)
|
| 2442 |
+
html.Span("Resolution for 3D visualization. Should be <= Grid Size (2^n).")
|
| 2443 |
+
|
| 2444 |
+
# Timesteps input
|
| 2445 |
+
with vuetify3.VTooltip(location="top"):
|
| 2446 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2447 |
+
vuetify3.VTextField(
|
| 2448 |
+
v_bind="props",
|
| 2449 |
+
label="Total Time",
|
| 2450 |
+
v_model=("qlbm_job_total_time", 3),
|
| 2451 |
+
type="number",
|
| 2452 |
+
density="compact",
|
| 2453 |
+
hide_details=True,
|
| 2454 |
+
color="primary",
|
| 2455 |
+
classes="mb-2",
|
| 2456 |
+
hint="Enter the total time T used when running the job",
|
| 2457 |
+
)
|
| 2458 |
+
html.Span("Total number of time steps in the uploaded job")
|
| 2459 |
+
|
| 2460 |
+
# Advanced options (flag qubits, mid-circuit measurement) - HIDDEN
|
| 2461 |
+
# Defaulting to True for both as per user request
|
| 2462 |
+
|
| 2463 |
+
# File upload and Generate button
|
| 2464 |
+
with vuetify3.VRow(dense=True, classes="align-center mt-2"):
|
| 2465 |
+
with vuetify3.VCol(cols=8):
|
| 2466 |
+
vuetify3.VFileInput(
|
| 2467 |
+
label="Upload Job Result (JSON)",
|
| 2468 |
+
v_model=("qlbm_job_upload", None),
|
| 2469 |
+
accept=".json",
|
| 2470 |
+
prepend_icon="mdi-file-upload",
|
| 2471 |
+
density="compact",
|
| 2472 |
+
hide_details=True,
|
| 2473 |
+
color="primary",
|
| 2474 |
+
show_size=True,
|
| 2475 |
+
clearable=True,
|
| 2476 |
+
)
|
| 2477 |
+
with vuetify3.VCol(cols=4):
|
| 2478 |
+
vuetify3.VBtn(
|
| 2479 |
+
text="Generate",
|
| 2480 |
+
color="secondary",
|
| 2481 |
+
variant="tonal",
|
| 2482 |
+
block=True,
|
| 2483 |
+
disabled=("!qlbm_job_upload || qlbm_job_is_processing", True),
|
| 2484 |
+
loading=("qlbm_job_is_processing", False),
|
| 2485 |
+
click=process_uploaded_job_result,
|
| 2486 |
+
prepend_icon="mdi-chart-box-outline",
|
| 2487 |
+
)
|
| 2488 |
+
|
| 2489 |
+
# Success message
|
| 2490 |
+
vuetify3.VAlert(
|
| 2491 |
+
v_if="qlbm_job_upload_success",
|
| 2492 |
+
type="success",
|
| 2493 |
+
variant="tonal",
|
| 2494 |
+
density="compact",
|
| 2495 |
+
closable=True,
|
| 2496 |
+
children=["{{ qlbm_job_upload_success }}"],
|
| 2497 |
+
classes="mt-2",
|
| 2498 |
+
)
|
| 2499 |
+
|
| 2500 |
+
# Error message
|
| 2501 |
+
vuetify3.VAlert(
|
| 2502 |
+
v_if="qlbm_job_upload_error",
|
| 2503 |
+
type="error",
|
| 2504 |
+
variant="tonal",
|
| 2505 |
+
density="compact",
|
| 2506 |
+
closable=True,
|
| 2507 |
+
children=["{{ qlbm_job_upload_error }}"],
|
| 2508 |
+
classes="mt-2",
|
| 2509 |
+
)
|
| 2510 |
|
| 2511 |
|
| 2512 |
def _build_visualization_panel(plotter):
|