Spaces:
Paused
Paused
Commit
·
10c45a9
1
Parent(s):
c27d6ee
Added Console Window
Browse files- README.md +9 -7
- em_trame.py +67 -27
- qlbm.py +130 -21
README.md
CHANGED
|
@@ -36,15 +36,17 @@ individual services so the browser never tries to contact `127.0.0.1` directly.
|
|
| 36 |
## CUDA-Q backend on CPU-only hosts
|
| 37 |
|
| 38 |
The 3D lattice Boltzmann solver relies on CUDA-Q, which expects a CUDA-capable GPU.
|
| 39 |
-
CPU-only runtimes such as Hugging Face Spaces
|
| 40 |
-
|
| 41 |
-
|
|
|
|
| 42 |
|
| 43 |
| Variable | Effect |
|
| 44 |
| --- | --- |
|
| 45 |
-
| `DISABLE_CUDAQ=1` |
|
| 46 |
| `FORCE_ENABLE_CUDAQ=1` | Attempt to load the CUDA-Q backend even if the platform was auto-detected as CPU-only. |
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
set `FORCE_ENABLE_CUDAQ=1`
|
|
|
|
|
|
| 36 |
## CUDA-Q backend on CPU-only hosts
|
| 37 |
|
| 38 |
The 3D lattice Boltzmann solver relies on CUDA-Q, which expects a CUDA-capable GPU.
|
| 39 |
+
CPU-only runtimes such as Hugging Face Spaces automatically fall back to a lightweight
|
| 40 |
+
"CPU demo" mode so the UI and preview still run. The plots update with an approximate
|
| 41 |
+
synthetic evolution and clearly indicate the active backend. Use these environment
|
| 42 |
+
variables to control the behavior:
|
| 43 |
|
| 44 |
| Variable | Effect |
|
| 45 |
| --- | --- |
|
| 46 |
+
| `DISABLE_CUDAQ=1` | Skip loading the CUDA-Q backend (the CPU demo remains available). |
|
| 47 |
| `FORCE_ENABLE_CUDAQ=1` | Attempt to load the CUDA-Q backend even if the platform was auto-detected as CPU-only. |
|
| 48 |
|
| 49 |
+
On GPU-enabled machines, leave both variables unset to run the full CUDA-Q solver. On
|
| 50 |
+
CPU-only hosts you can still explore the workflow in demo mode, or deploy to a GPU-
|
| 51 |
+
backed Space/local workstation and set `FORCE_ENABLE_CUDAQ=1` to activate the quantum
|
| 52 |
+
backend once hardware is available.
|
em_trame.py
CHANGED
|
@@ -98,6 +98,7 @@ state.update({
|
|
| 98 |
"qpu_monitor_gridpoints": "", # Derived gridpoints for QPU monitors
|
| 99 |
"qpu_monitor_samples": "(0.5, 0.5)", # User-facing normalized sample input
|
| 100 |
"qpu_monitor_sample_info": "",
|
|
|
|
| 101 |
# Square aspect for initial preview
|
| 102 |
"pyvista_view_style": "aspect-ratio: 1 / 1; width: 100%;",
|
| 103 |
"qpu_ts_fig": None,
|
|
@@ -139,6 +140,7 @@ state.update({
|
|
| 139 |
"status_type": "info", # info, success, warning, error
|
| 140 |
"simulation_progress": 0,
|
| 141 |
"show_progress": False,
|
|
|
|
| 142 |
})
|
| 143 |
|
| 144 |
# Ensure hole snap state exists
|
|
@@ -724,6 +726,7 @@ def run_simulation_only():
|
|
| 724 |
# Require selections before running
|
| 725 |
if not state.geometry_selection:
|
| 726 |
state.error_message = "Please select a geometry before running the simulation."
|
|
|
|
| 727 |
state.status_visible = True
|
| 728 |
state.status_message = "Error: Please select a geometry before running."
|
| 729 |
state.status_type = "error"
|
|
@@ -733,6 +736,7 @@ def run_simulation_only():
|
|
| 733 |
return
|
| 734 |
if not state.dist_type:
|
| 735 |
state.error_message = "Please select an initial state before running the simulation."
|
|
|
|
| 736 |
state.status_visible = True
|
| 737 |
state.status_message = "Error: Please select an initial state before running."
|
| 738 |
state.status_type = "error"
|
|
@@ -744,6 +748,7 @@ def run_simulation_only():
|
|
| 744 |
# Show status: Starting simulation
|
| 745 |
state.status_visible = True
|
| 746 |
state.status_message = "Initializing simulation..."
|
|
|
|
| 747 |
state.status_type = "info"
|
| 748 |
state.show_progress = True
|
| 749 |
state.simulation_progress = 0
|
|
@@ -1166,13 +1171,13 @@ def _build_sim_timeseries_plotly(field_type: str, positions, nx: int, times, sim
|
|
| 1166 |
)
|
| 1167 |
fig.update_xaxes(
|
| 1168 |
title_text="Time (s)", title_font=dict(size=22), tickfont=dict(size=16),
|
| 1169 |
-
showgrid=True, gridcolor="rgba(95,37,159
|
| 1170 |
showline=True, linewidth=1, linecolor="rgba(0,0,0,.2)",
|
| 1171 |
showspikes=True, spikemode='across', spikesnap='cursor'
|
| 1172 |
)
|
| 1173 |
fig.update_yaxes(
|
| 1174 |
title_text="Field Amplitude", title_font=dict(size=22), tickfont=dict(size=16),
|
| 1175 |
-
showgrid=True, gridcolor="rgba(95,37,159
|
| 1176 |
showline=True, linewidth=1, linecolor="rgba(0,0,0,.2)"
|
| 1177 |
)
|
| 1178 |
if max_abs > 0:
|
|
@@ -1755,6 +1760,20 @@ def handle_file_upload(uploaded_file_info, **kwargs):
|
|
| 1755 |
state.upload_status_message = f"File '{file_name}' uploaded."
|
| 1756 |
state.show_upload_status = True
|
| 1757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1758 |
def update_excitation_info_message():
|
| 1759 |
"""Calculates and displays the coordinate snapping message."""
|
| 1760 |
if state.nx is None or state.dist_type is None:
|
|
@@ -1852,14 +1871,28 @@ def on_time_change(time_val, **kwargs):
|
|
| 1852 |
idx = int(np.argmin(np.abs(times - float(time_val))))
|
| 1853 |
max_idx = len(data_frames[field]) - 1
|
| 1854 |
idx = max(0, min(idx, max_idx))
|
|
|
|
| 1855 |
z_data = data_frames[field][idx]
|
| 1856 |
-
|
| 1857 |
-
|
| 1858 |
-
|
| 1859 |
-
|
| 1860 |
-
|
| 1861 |
-
|
| 1862 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1863 |
def update_value_display(point):
|
| 1864 |
if current_mesh is None:
|
| 1865 |
return
|
|
@@ -1943,7 +1976,7 @@ def export_vtk_all_frames():
|
|
| 1943 |
if snapshot_times is None:
|
| 1944 |
raise ValueError("Snapshot times are unavailable.")
|
| 1945 |
|
| 1946 |
-
dl_dir = os.path.
|
| 1947 |
suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 1948 |
nx = int(state.nx)
|
| 1949 |
out_dir = os.path.join(dl_dir, f"vtk_sequence_{field}_nx{nx}_{suffix}")
|
|
@@ -2151,8 +2184,6 @@ def qpu_set_monitor_field(index, value):
|
|
| 2151 |
try:
|
| 2152 |
i = int(index)
|
| 2153 |
v = str(value).strip() if value is not None else "Ez"
|
| 2154 |
-
if v not in ("Ez", "Hx", "Hy"):
|
| 2155 |
-
v = "Ez"
|
| 2156 |
cfgs = list(state.qpu_monitor_configs or [])
|
| 2157 |
if 0 <= i < len(cfgs):
|
| 2158 |
cfgs[i]["field"] = v
|
|
@@ -2281,8 +2312,8 @@ def _rebuild_qpu_fig_others(selected_field: str, position_filter: str = "All pos
|
|
| 2281 |
color_hex = f"#{int(rgba[0]*255):02x}{int(rgba[1]*255):02x}{int(rgba[2]*255):02x}"
|
| 2282 |
fig.add_trace(go.Scatter(
|
| 2283 |
x=times, y=ys, mode='lines+markers', name=f"({px}, {py})",
|
| 2284 |
-
line=dict(color=color_hex, width=2.5, dash=
|
| 2285 |
-
marker=dict(size=7, symbol=
|
| 2286 |
hovertemplate=f"{sel} | t=%{{x:.3f}}s<br>Value=%{{y:.6g}}<extra>({px}, {py})</extra>",
|
| 2287 |
))
|
| 2288 |
fig.update_layout(title=f"IBM QPU Time Series (Other components: {sel})", height=660, width=900, margin=dict(l=50, r=30, t=50, b=50), hovermode="x unified", legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1, title_text=""))
|
|
@@ -2424,8 +2455,7 @@ with SinglePageLayout(server) as layout:
|
|
| 2424 |
children=["{{ hole_error_message }}"],
|
| 2425 |
classes="mt-1",
|
| 2426 |
)
|
| 2427 |
-
#
|
| 2428 |
-
# Cell 4: Excitation
|
| 2429 |
with vuetify3.VCard(classes="mb-1", style=("excitation_card_style", "font-size: 0.8rem;")):
|
| 2430 |
with vuetify3.VCardTitle("Excitation: Initial State", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2431 |
pass
|
|
@@ -2469,25 +2499,20 @@ with SinglePageLayout(server) as layout:
|
|
| 2469 |
style="white-space: pre-line;",
|
| 2470 |
)
|
| 2471 |
|
| 2472 |
-
# Cell
|
| 2473 |
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
|
| 2474 |
with vuetify3.VCardTitle("Material Properties (Medium)", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2475 |
pass
|
| 2476 |
with vuetify3.VCardText(classes="py-1 px-2"):
|
| 2477 |
with vuetify3.VRow(dense=True):
|
| 2478 |
-
with vuetify3.VCol():
|
| 2479 |
with vuetify3.VTooltip("Relative permittivity. ε_r = ε / ε₀. Default 1.0 (free space).", location="bottom", color="primary"):
|
| 2480 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2481 |
-
vuetify3.VTextField(v_bind="props", v_model=("coeff_permittivity", 1.0), label="
|
| 2482 |
-
with vuetify3.VCol():
|
| 2483 |
-
with vuetify3.VTooltip("Relative permeability. μ_r = μ / μ₀.
|
| 2484 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2485 |
-
vuetify3.VTextField(v_bind="props", v_model=("coeff_permeability", 1.0), label="
|
| 2486 |
-
|
| 2487 |
-
# New Cell: Temporal Settings (moved here)
|
| 2488 |
-
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
|
| 2489 |
-
with vuetify3.VCardTitle("Temporal Settings", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2490 |
-
pass
|
| 2491 |
with vuetify3.VCardText(classes="py-1 px-2"):
|
| 2492 |
with vuetify3.VTooltip("Sets the total duration for the simulation to run.", location="bottom", color="primary"):
|
| 2493 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
|
@@ -2766,6 +2791,21 @@ with SinglePageLayout(server) as layout:
|
|
| 2766 |
with vuetify3.VContainer(v_if="!geometry_selection", fluid=True, classes="flex-grow-1 d-flex align-center justify-center text-medium-emphasis"):
|
| 2767 |
vuetify3.VCardText("Select a geometry to display the preview and results.")
|
| 2768 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2769 |
# Status Window - Fixed at bottom right
|
| 2770 |
with vuetify3.VCard(
|
| 2771 |
v_if="status_visible",
|
|
|
|
| 98 |
"qpu_monitor_gridpoints": "", # Derived gridpoints for QPU monitors
|
| 99 |
"qpu_monitor_samples": "(0.5, 0.5)", # User-facing normalized sample input
|
| 100 |
"qpu_monitor_sample_info": "",
|
| 101 |
+
"console_output": "Console initialized.\n",
|
| 102 |
# Square aspect for initial preview
|
| 103 |
"pyvista_view_style": "aspect-ratio: 1 / 1; width: 100%;",
|
| 104 |
"qpu_ts_fig": None,
|
|
|
|
| 140 |
"status_type": "info", # info, success, warning, error
|
| 141 |
"simulation_progress": 0,
|
| 142 |
"show_progress": False,
|
| 143 |
+
"console_logs": "Console initialized...\n",
|
| 144 |
})
|
| 145 |
|
| 146 |
# Ensure hole snap state exists
|
|
|
|
| 726 |
# Require selections before running
|
| 727 |
if not state.geometry_selection:
|
| 728 |
state.error_message = "Please select a geometry before running the simulation."
|
| 729 |
+
log_to_console("Error: Please select a geometry before running.")
|
| 730 |
state.status_visible = True
|
| 731 |
state.status_message = "Error: Please select a geometry before running."
|
| 732 |
state.status_type = "error"
|
|
|
|
| 736 |
return
|
| 737 |
if not state.dist_type:
|
| 738 |
state.error_message = "Please select an initial state before running the simulation."
|
| 739 |
+
log_to_console("Error: Please select an initial state before running.")
|
| 740 |
state.status_visible = True
|
| 741 |
state.status_message = "Error: Please select an initial state before running."
|
| 742 |
state.status_type = "error"
|
|
|
|
| 748 |
# Show status: Starting simulation
|
| 749 |
state.status_visible = True
|
| 750 |
state.status_message = "Initializing simulation..."
|
| 751 |
+
log_to_console("Initializing simulation...")
|
| 752 |
state.status_type = "info"
|
| 753 |
state.show_progress = True
|
| 754 |
state.simulation_progress = 0
|
|
|
|
| 1171 |
)
|
| 1172 |
fig.update_xaxes(
|
| 1173 |
title_text="Time (s)", title_font=dict(size=22), tickfont=dict(size=16),
|
| 1174 |
+
showgrid=True, gridcolor="rgba(95,37,159,0.08)", zeroline=False,
|
| 1175 |
showline=True, linewidth=1, linecolor="rgba(0,0,0,.2)",
|
| 1176 |
showspikes=True, spikemode='across', spikesnap='cursor'
|
| 1177 |
)
|
| 1178 |
fig.update_yaxes(
|
| 1179 |
title_text="Field Amplitude", title_font=dict(size=22), tickfont=dict(size=16),
|
| 1180 |
+
showgrid=True, gridcolor="rgba(95,37,159,0.08)", zeroline=True, zerolinecolor="rgba(0,0,0,.25)",
|
| 1181 |
showline=True, linewidth=1, linecolor="rgba(0,0,0,.2)"
|
| 1182 |
)
|
| 1183 |
if max_abs > 0:
|
|
|
|
| 1760 |
state.upload_status_message = f"File '{file_name}' uploaded."
|
| 1761 |
state.show_upload_status = True
|
| 1762 |
|
| 1763 |
+
def log_message(message, level="INFO"):
|
| 1764 |
+
"""Append a message to the console log with a timestamp and level."""
|
| 1765 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 1766 |
+
new_entry = f"[{timestamp}] [{level}] {message}\n"
|
| 1767 |
+
state.console_logs += new_entry
|
| 1768 |
+
# Optional: Limit log size to prevent performance issues
|
| 1769 |
+
if len(state.console_logs) > 50000:
|
| 1770 |
+
state.console_logs = state.console_logs[-50000:]
|
| 1771 |
+
|
| 1772 |
+
def log_to_console(message):
|
| 1773 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 1774 |
+
new_line = f"[{timestamp}] {message}\n"
|
| 1775 |
+
state.console_output = (state.console_output or "") + new_line
|
| 1776 |
+
|
| 1777 |
def update_excitation_info_message():
|
| 1778 |
"""Calculates and displays the coordinate snapping message."""
|
| 1779 |
if state.nx is None or state.dist_type is None:
|
|
|
|
| 1871 |
idx = int(np.argmin(np.abs(times - float(time_val))))
|
| 1872 |
max_idx = len(data_frames[field]) - 1
|
| 1873 |
idx = max(0, min(idx, max_idx))
|
| 1874 |
+
|
| 1875 |
z_data = data_frames[field][idx]
|
| 1876 |
+
points = np.c_[X_grids[field].ravel(), Y_grids[field].ravel(), z_data.ravel() * z_scale]
|
| 1877 |
+
poly = pv.PolyData(points)
|
| 1878 |
+
mesh = poly.delaunay_2d()
|
| 1879 |
+
mesh['scalars'] = z_data.ravel()
|
| 1880 |
+
current_mesh = mesh
|
| 1881 |
+
plotter.add_mesh(mesh, scalars='scalars', clim=surface_clims[field], cmap="RdBu", show_scalar_bar=False, show_edges=True, edge_color='grey', line_width=0.5)
|
| 1882 |
+
plotter.add_scalar_bar(title=f"{field} Amplitude")
|
| 1883 |
+
try:
|
| 1884 |
+
plotter.disable_picking()
|
| 1885 |
+
except Exception:
|
| 1886 |
+
pass
|
| 1887 |
+
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 1888 |
+
plotter.add_axes()
|
| 1889 |
+
plotter.view_isometric()
|
| 1890 |
+
try:
|
| 1891 |
+
plotter.camera.parallel_projection = True
|
| 1892 |
+
except Exception:
|
| 1893 |
+
pass
|
| 1894 |
+
ctrl.view_update()
|
| 1895 |
+
|
| 1896 |
def update_value_display(point):
|
| 1897 |
if current_mesh is None:
|
| 1898 |
return
|
|
|
|
| 1976 |
if snapshot_times is None:
|
| 1977 |
raise ValueError("Snapshot times are unavailable.")
|
| 1978 |
|
| 1979 |
+
dl_dir = os.path.expanduser("~/Downloads")
|
| 1980 |
suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 1981 |
nx = int(state.nx)
|
| 1982 |
out_dir = os.path.join(dl_dir, f"vtk_sequence_{field}_nx{nx}_{suffix}")
|
|
|
|
| 2184 |
try:
|
| 2185 |
i = int(index)
|
| 2186 |
v = str(value).strip() if value is not None else "Ez"
|
|
|
|
|
|
|
| 2187 |
cfgs = list(state.qpu_monitor_configs or [])
|
| 2188 |
if 0 <= i < len(cfgs):
|
| 2189 |
cfgs[i]["field"] = v
|
|
|
|
| 2312 |
color_hex = f"#{int(rgba[0]*255):02x}{int(rgba[1]*255):02x}{int(rgba[2]*255):02x}"
|
| 2313 |
fig.add_trace(go.Scatter(
|
| 2314 |
x=times, y=ys, mode='lines+markers', name=f"({px}, {py})",
|
| 2315 |
+
line=dict(color=color_hex, width=2.5, dash=dash_styles[i % len(dash_styles)]),
|
| 2316 |
+
marker=dict(size=7, symbol=marker_symbols[i % len(marker_symbols)], color=color_hex),
|
| 2317 |
hovertemplate=f"{sel} | t=%{{x:.3f}}s<br>Value=%{{y:.6g}}<extra>({px}, {py})</extra>",
|
| 2318 |
))
|
| 2319 |
fig.update_layout(title=f"IBM QPU Time Series (Other components: {sel})", height=660, width=900, margin=dict(l=50, r=30, t=50, b=50), hovermode="x unified", legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1, title_text=""))
|
|
|
|
| 2455 |
children=["{{ hole_error_message }}"],
|
| 2456 |
classes="mt-1",
|
| 2457 |
)
|
| 2458 |
+
# Cell 3: Excitation
|
|
|
|
| 2459 |
with vuetify3.VCard(classes="mb-1", style=("excitation_card_style", "font-size: 0.8rem;")):
|
| 2460 |
with vuetify3.VCardTitle("Excitation: Initial State", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2461 |
pass
|
|
|
|
| 2499 |
style="white-space: pre-line;",
|
| 2500 |
)
|
| 2501 |
|
| 2502 |
+
# Cell 4: Medium
|
| 2503 |
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
|
| 2504 |
with vuetify3.VCardTitle("Material Properties (Medium)", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2505 |
pass
|
| 2506 |
with vuetify3.VCardText(classes="py-1 px-2"):
|
| 2507 |
with vuetify3.VRow(dense=True):
|
| 2508 |
+
with vuetify3.VCol(cols="6"):
|
| 2509 |
with vuetify3.VTooltip("Relative permittivity. ε_r = ε / ε₀. Default 1.0 (free space).", location="bottom", color="primary"):
|
| 2510 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2511 |
+
vuetify3.VTextField(v_bind="props", v_model=("coeff_permittivity", 1.0), label="Permittivity (εr)", type="number", step="0.1", density="compact", color="primary")
|
| 2512 |
+
with vuetify3.VCol(cols="6"):
|
| 2513 |
+
with vuetify3.VTooltip("Relative permeability. μ_r = μ / μ₀. Default 1.0 (non-magnetic).", location="bottom", color="primary"):
|
| 2514 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 2515 |
+
vuetify3.VTextField(v_bind="props", v_model=("coeff_permeability", 1.0), label="Permeability (μr)", type="number", step="0.1", density="compact", color="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2516 |
with vuetify3.VCardText(classes="py-1 px-2"):
|
| 2517 |
with vuetify3.VTooltip("Sets the total duration for the simulation to run.", location="bottom", color="primary"):
|
| 2518 |
with vuetify3.Template(v_slot_activator="{ props }"):
|
|
|
|
| 2791 |
with vuetify3.VContainer(v_if="!geometry_selection", fluid=True, classes="flex-grow-1 d-flex align-center justify-center text-medium-emphasis"):
|
| 2792 |
vuetify3.VCardText("Select a geometry to display the preview and results.")
|
| 2793 |
|
| 2794 |
+
# Console Window
|
| 2795 |
+
with vuetify3.VCard(classes="mt-2", style="font-size: 0.8rem;"):
|
| 2796 |
+
with vuetify3.VCardTitle("Console Output", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
|
| 2797 |
+
pass
|
| 2798 |
+
with vuetify3.VCardText(classes="py-1 px-2", style="height: 150px; overflow-y: auto; background-color: #f5f5f5; font-family: monospace;"):
|
| 2799 |
+
vuetify3.VTextarea(
|
| 2800 |
+
v_model=("console_output", ""),
|
| 2801 |
+
readonly=True,
|
| 2802 |
+
auto_grow=False,
|
| 2803 |
+
rows=6,
|
| 2804 |
+
variant="plain",
|
| 2805 |
+
hide_details=True,
|
| 2806 |
+
style="font-family: monospace; width: 100%; height: 100%;"
|
| 2807 |
+
)
|
| 2808 |
+
|
| 2809 |
# Status Window - Fixed at bottom right
|
| 2810 |
with vuetify3.VCard(
|
| 2811 |
v_if="status_visible",
|
qlbm.py
CHANGED
|
@@ -48,6 +48,8 @@ def _should_disable_quantum_backend():
|
|
| 48 |
|
| 49 |
simulate_qlbm_3D_and_animate = None
|
| 50 |
_SIMULATION_BACKEND_DISABLED, _SIMULATION_DISABLED_REASON = _should_disable_quantum_backend()
|
|
|
|
|
|
|
| 51 |
|
| 52 |
if not _SIMULATION_BACKEND_DISABLED:
|
| 53 |
try:
|
|
@@ -69,6 +71,23 @@ _SIMULATION_BACKEND_READY = simulate_qlbm_3D_and_animate is not None
|
|
| 69 |
if not _SIMULATION_BACKEND_READY and _SIMULATION_DISABLED_REASON:
|
| 70 |
print(f"Warning: {_SIMULATION_DISABLED_REASON}")
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
# --- Server Setup ---
|
| 73 |
server = get_server()
|
| 74 |
state, ctrl = server.state, server.controller
|
|
@@ -121,8 +140,9 @@ state.update({
|
|
| 121 |
"simulation_has_run": False,
|
| 122 |
"time_val": 0,
|
| 123 |
"max_time_step": 0,
|
| 124 |
-
"simulation_backend_ready":
|
| 125 |
-
"
|
|
|
|
| 126 |
# Workflow guidance styles
|
| 127 |
"workflow_step": 0,
|
| 128 |
"overview_card_style": _WORKFLOW_BASE_STYLE,
|
|
@@ -422,6 +442,84 @@ def make_velocity_func(expr):
|
|
| 422 |
return np.zeros_like(x) if isinstance(x, np.ndarray) else 0.0
|
| 423 |
return func
|
| 424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
def get_geometry_figure():
|
| 426 |
"""Generates a 3D Plotly figure for the selected geometry."""
|
| 427 |
geom = state.geometry_selection
|
|
@@ -516,8 +614,8 @@ def update_geometry_view():
|
|
| 516 |
def run_simulation():
|
| 517 |
global simulation_data_frames, simulation_times, current_grid_object
|
| 518 |
|
| 519 |
-
if
|
| 520 |
-
state.run_error = _SIMULATION_DISABLED_REASON or "Simulation
|
| 521 |
return
|
| 522 |
|
| 523 |
state.is_running = True
|
|
@@ -537,20 +635,30 @@ def run_simulation():
|
|
| 537 |
vy_func = make_velocity_func(state.vy_expr)
|
| 538 |
vz_func = make_velocity_func(state.vz_expr)
|
| 539 |
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
|
| 555 |
# Force Blues colormap
|
| 556 |
if grid_obj:
|
|
@@ -1011,12 +1119,13 @@ with SinglePageLayout(server) as layout:
|
|
| 1011 |
click=run_simulation,
|
| 1012 |
style=("is_running ? '' : 'background-color:#87CEFA;'", ""),
|
| 1013 |
)
|
|
|
|
| 1014 |
vuetify3.VAlert(
|
| 1015 |
-
v_if="
|
| 1016 |
-
type="
|
| 1017 |
variant="tonal",
|
| 1018 |
density="compact",
|
| 1019 |
-
children=["{{
|
| 1020 |
classes="mt-2",
|
| 1021 |
)
|
| 1022 |
with vuetify3.VRow(dense=True, classes="mt-2"):
|
|
|
|
| 48 |
|
| 49 |
simulate_qlbm_3D_and_animate = None
|
| 50 |
_SIMULATION_BACKEND_DISABLED, _SIMULATION_DISABLED_REASON = _should_disable_quantum_backend()
|
| 51 |
+
_CPU_DEMO_AVAILABLE = True
|
| 52 |
+
_CPU_DEMO_MAX_GRID = 48
|
| 53 |
|
| 54 |
if not _SIMULATION_BACKEND_DISABLED:
|
| 55 |
try:
|
|
|
|
| 71 |
if not _SIMULATION_BACKEND_READY and _SIMULATION_DISABLED_REASON:
|
| 72 |
print(f"Warning: {_SIMULATION_DISABLED_REASON}")
|
| 73 |
|
| 74 |
+
if _SIMULATION_BACKEND_READY:
|
| 75 |
+
_SIMULATION_BACKEND_NOTE = ""
|
| 76 |
+
_SIMULATION_MODE_LABEL = "Quantum CUDA-Q backend"
|
| 77 |
+
else:
|
| 78 |
+
if _CPU_DEMO_AVAILABLE:
|
| 79 |
+
reason = _SIMULATION_DISABLED_REASON or "Quantum backend is unavailable in this environment."
|
| 80 |
+
_SIMULATION_BACKEND_NOTE = (
|
| 81 |
+
f"CPU demo mode active ({reason}). Results are approximate. "
|
| 82 |
+
"Set FORCE_ENABLE_CUDAQ=1 on a GPU host to use the quantum backend."
|
| 83 |
+
)
|
| 84 |
+
_SIMULATION_MODE_LABEL = "CPU demo backend"
|
| 85 |
+
else:
|
| 86 |
+
_SIMULATION_BACKEND_NOTE = _SIMULATION_DISABLED_REASON or "Simulation backend unavailable."
|
| 87 |
+
_SIMULATION_MODE_LABEL = "Unavailable"
|
| 88 |
+
|
| 89 |
+
_SIMULATION_CAN_RUN = _SIMULATION_BACKEND_READY or _CPU_DEMO_AVAILABLE
|
| 90 |
+
|
| 91 |
# --- Server Setup ---
|
| 92 |
server = get_server()
|
| 93 |
state, ctrl = server.state, server.controller
|
|
|
|
| 140 |
"simulation_has_run": False,
|
| 141 |
"time_val": 0,
|
| 142 |
"max_time_step": 0,
|
| 143 |
+
"simulation_backend_ready": _SIMULATION_CAN_RUN,
|
| 144 |
+
"simulation_backend_note": _SIMULATION_BACKEND_NOTE,
|
| 145 |
+
"simulation_backend_mode": _SIMULATION_MODE_LABEL,
|
| 146 |
# Workflow guidance styles
|
| 147 |
"workflow_step": 0,
|
| 148 |
"overview_card_style": _WORKFLOW_BASE_STYLE,
|
|
|
|
| 442 |
return np.zeros_like(x) if isinstance(x, np.ndarray) else 0.0
|
| 443 |
return func
|
| 444 |
|
| 445 |
+
|
| 446 |
+
def _safe_velocity_sample(func) -> float:
|
| 447 |
+
try:
|
| 448 |
+
val = func(0.5, 0.5, 0.5)
|
| 449 |
+
if isinstance(val, np.ndarray):
|
| 450 |
+
val = float(np.mean(val))
|
| 451 |
+
return float(val)
|
| 452 |
+
except Exception:
|
| 453 |
+
return 0.0
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def _cpu_distribution_field(distribution_type: str, Xi, Yi, Zi, grid_size: int, drift, phase_fraction: float):
|
| 457 |
+
if distribution_type == "Sinusoidal":
|
| 458 |
+
kx = max(1.0, round(float(state.sine_k_x))) if hasattr(state, "sine_k_x") else 1.0
|
| 459 |
+
ky = max(1.0, round(float(state.sine_k_y))) if hasattr(state, "sine_k_y") else 1.0
|
| 460 |
+
kz = max(1.0, round(float(state.sine_k_z))) if hasattr(state, "sine_k_z") else 1.0
|
| 461 |
+
x_term = np.sin((np.mod(Xi + drift[0], grid_size)) * 2 * np.pi * kx / grid_size)
|
| 462 |
+
y_term = np.sin((np.mod(Yi + drift[1], grid_size)) * 2 * np.pi * ky / grid_size)
|
| 463 |
+
z_term = np.sin((np.mod(Zi + drift[2], grid_size)) * 2 * np.pi * kz / grid_size)
|
| 464 |
+
field = x_term * y_term * z_term + 1.0
|
| 465 |
+
else:
|
| 466 |
+
# Gaussian (default fallback)
|
| 467 |
+
nx_val = max(1.0, float(state.nx)) if hasattr(state, "nx") else float(grid_size)
|
| 468 |
+
cx = float(state.gauss_cx) if hasattr(state, "gauss_cx") else nx_val / 2
|
| 469 |
+
cy = float(state.gauss_cy) if hasattr(state, "gauss_cy") else nx_val / 2
|
| 470 |
+
cz = float(state.gauss_cz) if hasattr(state, "gauss_cz") else nx_val / 2
|
| 471 |
+
sigma = float(state.gauss_sigma) if hasattr(state, "gauss_sigma") else nx_val / 6
|
| 472 |
+
scale = (grid_size - 1) / nx_val if nx_val else 1.0
|
| 473 |
+
cx = cx * scale + drift[0]
|
| 474 |
+
cy = cy * scale + drift[1]
|
| 475 |
+
cz = cz * scale + drift[2]
|
| 476 |
+
sigma = max(1.0, sigma * scale)
|
| 477 |
+
field = np.exp(-(((Xi - cx) ** 2 + (Yi - cy) ** 2 + (Zi - cz) ** 2) / (2 * sigma ** 2))) * 1.8 + 0.2
|
| 478 |
+
|
| 479 |
+
modulation = 0.15 * np.sin(2 * np.pi * phase_fraction + (Xi + Yi + Zi) * np.pi / max(1, grid_size))
|
| 480 |
+
return field + modulation
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def _run_cpu_demo_simulation(grid_size: int, T: int, distribution_type: str, vx_func, vy_func, vz_func):
|
| 484 |
+
grid_size = int(max(8, min(grid_size, _CPU_DEMO_MAX_GRID)))
|
| 485 |
+
idx_coords = np.linspace(0, grid_size - 1, grid_size, dtype=np.float32)
|
| 486 |
+
Xi, Yi, Zi = np.meshgrid(idx_coords, idx_coords, idx_coords, indexing='ij')
|
| 487 |
+
geom_coords = np.linspace(0, 1, grid_size, dtype=np.float32)
|
| 488 |
+
Xg, Yg, Zg = np.meshgrid(geom_coords, geom_coords, geom_coords, indexing='ij')
|
| 489 |
+
|
| 490 |
+
if T <= 0:
|
| 491 |
+
target = 1.0
|
| 492 |
+
else:
|
| 493 |
+
target = float(T)
|
| 494 |
+
num_frames = min(30, max(2, int(min(target, 20)) + 1))
|
| 495 |
+
timeline = list(np.linspace(0.0, target, num_frames))
|
| 496 |
+
if len(timeline) < 2:
|
| 497 |
+
timeline.append(target)
|
| 498 |
+
|
| 499 |
+
vx = _safe_velocity_sample(vx_func)
|
| 500 |
+
vy = _safe_velocity_sample(vy_func)
|
| 501 |
+
vz = _safe_velocity_sample(vz_func)
|
| 502 |
+
drift_scale = 0.25 * grid_size
|
| 503 |
+
|
| 504 |
+
frames = []
|
| 505 |
+
for idx, t_val in enumerate(timeline):
|
| 506 |
+
phase_fraction = idx / (len(timeline) - 1) if len(timeline) > 1 else 0.0
|
| 507 |
+
drift = (
|
| 508 |
+
vx * phase_fraction * drift_scale,
|
| 509 |
+
vy * phase_fraction * drift_scale,
|
| 510 |
+
vz * phase_fraction * drift_scale,
|
| 511 |
+
)
|
| 512 |
+
field = _cpu_distribution_field(distribution_type, Xi, Yi, Zi, grid_size, drift, phase_fraction)
|
| 513 |
+
frames.append(field.astype(np.float32))
|
| 514 |
+
|
| 515 |
+
grid = pv.StructuredGrid()
|
| 516 |
+
grid.points = np.column_stack((Xg.ravel(), Yg.ravel(), Zg.ravel()))
|
| 517 |
+
grid.dimensions = [grid_size, grid_size, grid_size]
|
| 518 |
+
grid["scalars"] = frames[0].ravel()
|
| 519 |
+
|
| 520 |
+
times = [float(t) for t in timeline]
|
| 521 |
+
return frames, times, grid
|
| 522 |
+
|
| 523 |
def get_geometry_figure():
|
| 524 |
"""Generates a 3D Plotly figure for the selected geometry."""
|
| 525 |
geom = state.geometry_selection
|
|
|
|
| 614 |
def run_simulation():
|
| 615 |
global simulation_data_frames, simulation_times, current_grid_object
|
| 616 |
|
| 617 |
+
if not _SIMULATION_CAN_RUN:
|
| 618 |
+
state.run_error = _SIMULATION_DISABLED_REASON or "Simulation backend is not available on this platform."
|
| 619 |
return
|
| 620 |
|
| 621 |
state.is_running = True
|
|
|
|
| 635 |
vy_func = make_velocity_func(state.vy_expr)
|
| 636 |
vz_func = make_velocity_func(state.vz_expr)
|
| 637 |
|
| 638 |
+
if simulate_qlbm_3D_and_animate is not None:
|
| 639 |
+
# 2a. Run CUDA-Q Simulation
|
| 640 |
+
plotter.clear()
|
| 641 |
+
_, frames, times, grid_obj = simulate_qlbm_3D_and_animate(
|
| 642 |
+
num_reg_qubits=num_reg_qubits,
|
| 643 |
+
T=T,
|
| 644 |
+
distribution_type=distribution_type,
|
| 645 |
+
vx_input=vx_func,
|
| 646 |
+
vy_input=vy_func,
|
| 647 |
+
vz_input=vz_func,
|
| 648 |
+
boundary_condition=boundary_condition,
|
| 649 |
+
plotter=plotter,
|
| 650 |
+
add_slider=False
|
| 651 |
+
)
|
| 652 |
+
else:
|
| 653 |
+
# 2b. CPU Demo Simulation (approximate)
|
| 654 |
+
frames, times, grid_obj = _run_cpu_demo_simulation(
|
| 655 |
+
grid_size=grid_size,
|
| 656 |
+
T=T,
|
| 657 |
+
distribution_type=distribution_type or "Sinusoidal",
|
| 658 |
+
vx_func=vx_func,
|
| 659 |
+
vy_func=vy_func,
|
| 660 |
+
vz_func=vz_func,
|
| 661 |
+
)
|
| 662 |
|
| 663 |
# Force Blues colormap
|
| 664 |
if grid_obj:
|
|
|
|
| 1119 |
click=run_simulation,
|
| 1120 |
style=("is_running ? '' : 'background-color:#87CEFA;'", ""),
|
| 1121 |
)
|
| 1122 |
+
html.Div("Backend: {{ simulation_backend_mode }}", classes="text-caption text-medium-emphasis mt-2")
|
| 1123 |
vuetify3.VAlert(
|
| 1124 |
+
v_if="simulation_backend_note",
|
| 1125 |
+
type="info",
|
| 1126 |
variant="tonal",
|
| 1127 |
density="compact",
|
| 1128 |
+
children=["{{ simulation_backend_note }}"],
|
| 1129 |
classes="mt-2",
|
| 1130 |
)
|
| 1131 |
with vuetify3.VRow(dense=True, classes="mt-2"):
|