Spaces:
Paused
Paused
harishaseebat92
commited on
Commit
·
75a1323
1
Parent(s):
f7e81bd
Merged QLBM Application and Added features in EM_Trame
Browse files- .dockerignore +30 -0
- .gitattributes +1 -1
- .gitignore +2 -0
- ansys-part-of-synopsys-logo.svg +39 -0
- app.py +34 -1506
- em_trame.py +0 -0
- pages/__init__.py +4 -0
- pages/__pycache__/__init__.cpython-311.pyc +0 -0
- pages/__pycache__/__init__.cpython-313.pyc +0 -0
- pages/__pycache__/em_page.cpython-311.pyc +0 -0
- pages/__pycache__/em_page.cpython-313.pyc +0 -0
- pages/__pycache__/qlbm_page.cpython-311.pyc +0 -0
- pages/__pycache__/qlbm_page.cpython-313.pyc +0 -0
- pages/em_page.py +62 -0
- pages/qlbm_page.py +203 -0
- qlbm.py +285 -0
- synopsys-logo-color-rgb.png +0 -0
- synopsys-logo-color-rgb.svg +0 -42
- utils/__pycache__/base_functions.cpython-311.pyc +0 -0
- utils/__pycache__/base_functions.cpython-313.pyc +0 -0
- utils/__pycache__/delta_impulse_generator.cpython-311.pyc +0 -0
- utils/__pycache__/delta_impulse_generator.cpython-313.pyc +0 -0
- utils/base_functions.py +443 -0
- utils/delta_impulse_generator.py +468 -0
- utils/fdtd_demo.ipynb +326 -0
- utils/tempCodeRunnerFile.py +1 -0
.dockerignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
.Python
|
| 6 |
+
*.so
|
| 7 |
+
*.egg
|
| 8 |
+
*.egg-info/
|
| 9 |
+
dist/
|
| 10 |
+
build/
|
| 11 |
+
.git/
|
| 12 |
+
.gitignore
|
| 13 |
+
.gitattributes
|
| 14 |
+
venv/
|
| 15 |
+
env/
|
| 16 |
+
ENV/
|
| 17 |
+
.vscode/
|
| 18 |
+
.idea/
|
| 19 |
+
*.swp
|
| 20 |
+
*.swo
|
| 21 |
+
*~
|
| 22 |
+
.DS_Store
|
| 23 |
+
Thumbs.db
|
| 24 |
+
*.md
|
| 25 |
+
README.md
|
| 26 |
+
current_requirements.txt
|
| 27 |
+
tempCodeRunnerFile.py
|
| 28 |
+
em_trame1.py
|
| 29 |
+
em_tramefinal.py
|
| 30 |
+
trial.py
|
.gitattributes
CHANGED
|
@@ -33,4 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
-
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
synopsys-logo-color-rgb.jpg
|
ansys-part-of-synopsys-logo.svg
ADDED
|
|
app.py
CHANGED
|
@@ -1,86 +1,27 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
-
import re
|
| 3 |
-
import pyvista as pv
|
| 4 |
-
import webbrowser
|
| 5 |
-
import threading
|
| 6 |
-
import base64
|
| 7 |
-
|
| 8 |
from trame.app import get_server
|
| 9 |
from trame_vuetify.ui.vuetify3 import SinglePageLayout
|
| 10 |
from trame_vuetify.widgets import vuetify3
|
| 11 |
-
from pyvista.trame.ui import plotter_ui
|
| 12 |
-
import plotly.graph_objects as go
|
| 13 |
-
from trame_plotly.widgets import plotly as plotly_widgets
|
| 14 |
-
import os
|
| 15 |
-
from datetime import datetime
|
| 16 |
from trame.widgets import html as trame_html
|
|
|
|
| 17 |
|
| 18 |
-
|
| 19 |
-
from
|
| 20 |
-
|
| 21 |
-
from delta_impulse_generator import *
|
| 22 |
-
|
| 23 |
-
# Set PyVista to use off-screen rendering for Trame
|
| 24 |
-
pv.OFF_SCREEN = True
|
| 25 |
|
| 26 |
-
#
|
| 27 |
server = get_server()
|
| 28 |
state, ctrl = server.state, server.controller
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
state.
|
| 32 |
-
"dist_type": None, "impulse_x": 0.5, "impulse_y": 0.5,
|
| 33 |
-
"peak_pair": "(0.5, 0.5)",
|
| 34 |
-
"mu_x": 0.5, "mu_y": 0.5, "sigma_x": 0.25, "sigma_y": 0.15,
|
| 35 |
-
"mu_pair": "(0.5, 0.5)", # Gaussian Mu as normalized pair string
|
| 36 |
-
"sigma_pair": "(0.25, 0.15)", # Gaussian Sigma as normalized pair string
|
| 37 |
-
"nx": None, "T": 10.0, "time_val": 0.0,
|
| 38 |
-
"L": 1.0, # Side length used for coordinate conversion
|
| 39 |
-
"output_type": "Surface Plot",
|
| 40 |
-
"surface_field": "Ez",
|
| 41 |
-
"timeseries_field": "Ez",
|
| 42 |
-
"timeseries_points": "(8, 8), (10, 8)",
|
| 43 |
-
"error_message": "",
|
| 44 |
-
"is_running": False,
|
| 45 |
-
"simulation_has_run": False,
|
| 46 |
-
"geometry_selection": None,
|
| 47 |
-
"show_upload_dialog": False,
|
| 48 |
-
"uploaded_file_info": None,
|
| 49 |
-
"show_upload_status": False,
|
| 50 |
-
"upload_status_message": "",
|
| 51 |
-
"coeff_permittivity": 1.0, # Relative permittivity (ε_r)
|
| 52 |
-
"coeff_permeability": 1.0, # Relative permeability (μ_r)
|
| 53 |
-
"run_button_text": "Run Simulation",
|
| 54 |
-
"backend_type": "Simulator",
|
| 55 |
-
"selected_simulator": "IBM Qiskit simulator",
|
| 56 |
-
"selected_qpu": "IBM QPU",
|
| 57 |
-
"stop_button_disabled": True, # Stop button is initially disabled
|
| 58 |
-
"export_format": "vtk", # Dummy export format for Surface Plot
|
| 59 |
-
"nx_slider_index": None, # No selection until user chooses on the slider
|
| 60 |
-
"show_export_status": False,
|
| 61 |
-
"export_status_message": "",
|
| 62 |
-
"logo_src": None,
|
| 63 |
-
# Dummy geometry-hole controls (UI only)
|
| 64 |
-
"hole_size_edge": 0.2, # edge length in [0, 1]
|
| 65 |
-
"hole_center_x": 0.5, # center X in (0, 1)
|
| 66 |
-
"hole_center_y": 0.5, # center Y in (0, 1)
|
| 67 |
-
"hole_center_pair": "(0.5, 0.5)", # bracket-format center for dropdown
|
| 68 |
-
"hole_error_message": "", # validation message
|
| 69 |
-
"excitation_error_message": "", # parse/validation for Gaussian pair inputs
|
| 70 |
-
"excitation_info_message": "", # Snapping info for excitation coordinates
|
| 71 |
-
"dt_user": 0.1, # Dummy Δt input from user (seconds)
|
| 72 |
-
"temporal_warning": "", # Warning message for invalid Δt
|
| 73 |
-
# Square aspect for initial preview
|
| 74 |
-
"pyvista_view_style": "aspect-ratio: 1 / 1; width: 100%;",
|
| 75 |
-
})
|
| 76 |
|
| 77 |
-
#
|
| 78 |
-
|
| 79 |
|
| 80 |
-
|
| 81 |
-
def load_logo_data_uri():
|
| 82 |
base_dir = os.path.dirname(__file__)
|
| 83 |
candidates = [
|
|
|
|
| 84 |
os.path.join(base_dir, "synopsys-logo-color-rgb.svg"),
|
| 85 |
os.path.join(base_dir, "synopsys-logo-color-rgb.png"),
|
| 86 |
os.path.join(base_dir, "synopsys-logo-color-rgb.jpg"),
|
|
@@ -94,1454 +35,41 @@ def load_logo_data_uri():
|
|
| 94 |
return f"data:{mime};base64,{b64}"
|
| 95 |
return None
|
| 96 |
|
| 97 |
-
state
|
| 98 |
-
|
| 99 |
-
# --- Global PyVista and Data Variables ---
|
| 100 |
-
plotter = pv.Plotter()
|
| 101 |
-
simulation_data = None
|
| 102 |
-
current_mesh = None
|
| 103 |
-
data_frames = None
|
| 104 |
-
z_scale = 1.0
|
| 105 |
-
X_grids, Y_grids = {}, {}
|
| 106 |
-
surface_clims = {}
|
| 107 |
-
stop_simulation = False # Flag to stop running simulation
|
| 108 |
-
snapshot_times = None # Times corresponding to saved frames (user snapshots)
|
| 109 |
-
|
| 110 |
-
# --- Constants ---
|
| 111 |
-
GRID_SIZES = ["16", "32", "64", "128", "256", "512"]
|
| 112 |
-
|
| 113 |
-
# --- Plotting and Simulation Logic ---
|
| 114 |
-
def get_time_series_chart(simulation_data, field_type, positions, nx, times):
|
| 115 |
-
# times: 1D array of snapshot times aligning with simulation_data frames
|
| 116 |
-
n_frames = int(simulation_data.shape[0]) if simulation_data is not None else 0
|
| 117 |
-
time_axis = np.asarray(times) if times is not None else np.arange(n_frames)
|
| 118 |
-
chart = pv.Chart2D()
|
| 119 |
-
if (field_type == 'Ez'):
|
| 120 |
-
grid_width, grid_height = nx, nx
|
| 121 |
-
elif (field_type == 'Hx'):
|
| 122 |
-
grid_width, grid_height = nx, nx - 1
|
| 123 |
-
else:
|
| 124 |
-
grid_width, grid_height = nx - 1, nx
|
| 125 |
-
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
|
| 126 |
-
for i, (pos_x, pos_y) in enumerate(positions):
|
| 127 |
-
if not (0 <= pos_x < grid_width and 0 <= pos_y < grid_height):
|
| 128 |
-
print(f"Warning: Skipping invalid position {(pos_x, pos_y)} for {field_type}")
|
| 129 |
-
continue
|
| 130 |
-
if field_type == 'Ez': values = simulation_data[:, pos_y * grid_width + pos_x]
|
| 131 |
-
elif field_type == 'Hx': values = simulation_data[:, 2*nx*nx : 3*nx*nx-nx].reshape(n_frames, grid_height, grid_width)[:, pos_y, pos_x]
|
| 132 |
-
else:
|
| 133 |
-
mask = np.arange(1, nx * nx + 1) % nx != 0
|
| 134 |
-
raw_block = simulation_data[:, -nx*nx:]
|
| 135 |
-
values = [raw_block[t, mask].reshape(nx, nx - 1)[pos_y, pos_x] for t in range(n_frames)]
|
| 136 |
-
chart.line(time_axis, values, label=f'{field_type} at ({pos_x}, {pos_y})', color=colors[i % len(colors)])
|
| 137 |
-
chart.x_label = "Time (s)"; chart.y_label = "Field Amplitude"; chart.title = f"Time Evolution of {field_type}"
|
| 138 |
-
return chart
|
| 139 |
-
|
| 140 |
-
def setup_surface_plot_data(simulation_data, nx):
|
| 141 |
-
global data_frames, z_scale, X_grids, Y_grids, surface_clims
|
| 142 |
-
mask = np.arange(1, nx * nx + 1) % nx != 0
|
| 143 |
-
data_frames = {'Ez': [], 'Hx': [], 'Hy': []}
|
| 144 |
-
surface_clims = {'Ez': [np.inf, -np.inf], 'Hx': [np.inf, -np.inf], 'Hy': [np.inf, -np.inf]}
|
| 145 |
-
for u in simulation_data:
|
| 146 |
-
ez, hx, hy = u[:nx*nx].reshape(nx,nx), u[2*nx*nx:3*nx*nx-nx].reshape(nx-1,nx), u[-nx*nx:][mask].reshape(nx,nx-1)
|
| 147 |
-
data_frames['Ez'].append(ez); data_frames['Hx'].append(hx); data_frames['Hy'].append(hy)
|
| 148 |
-
if ez.size > 0: surface_clims['Ez'][0], surface_clims['Ez'][1] = min(surface_clims['Ez'][0], ez.min()), max(surface_clims['Ez'][1], ez.max())
|
| 149 |
-
if hx.size > 0: surface_clims['Hx'][0], surface_clims['Hx'][1] = min(surface_clims['Hx'][0], hx.min()), max(surface_clims['Hx'][1], hx.max())
|
| 150 |
-
if hy.size > 0: surface_clims['Hy'][0], surface_clims['Hy'][1] = min(surface_clims['Hy'][0], hy.min()), max(surface_clims['Hy'][1], hy.max())
|
| 151 |
-
for key in surface_clims:
|
| 152 |
-
if surface_clims[key][0] == surface_clims[key][1]: surface_clims[key][0] -= 1e-9; surface_clims[key][1] += 1e-9
|
| 153 |
-
# Revert to integer grid coordinates (like app.py) to keep output plots in grids
|
| 154 |
-
x, y, x_m1, y_m1 = np.arange(nx), np.arange(nx), np.arange(nx-1), np.arange(nx-1)
|
| 155 |
-
X_grids['Ez'], Y_grids['Ez'] = np.meshgrid(x, y)
|
| 156 |
-
X_grids['Hx'], Y_grids['Hx'] = np.meshgrid(x, y_m1)
|
| 157 |
-
X_grids['Hy'], Y_grids['Hy'] = np.meshgrid(x_m1, y)
|
| 158 |
-
max_abs = max(abs(v) for pair in surface_clims.values() for v in pair if v is not np.inf and v is not -np.inf)
|
| 159 |
-
z_scale = (nx / 2) / max(max_abs, 1e-9)
|
| 160 |
-
|
| 161 |
-
def run_simulation_only():
|
| 162 |
-
global simulation_data, current_mesh, snapshot_times, stop_simulation
|
| 163 |
-
# Require selections before running
|
| 164 |
-
if not state.geometry_selection:
|
| 165 |
-
state.error_message = "Please select a geometry before running the simulation."
|
| 166 |
-
state.is_running = False
|
| 167 |
-
state.run_button_text = "Run Simulation"
|
| 168 |
-
return
|
| 169 |
-
if not state.dist_type:
|
| 170 |
-
state.error_message = "Please select an initial state before running the simulation."
|
| 171 |
-
state.is_running = False
|
| 172 |
-
state.run_button_text = "Run Simulation"
|
| 173 |
-
return
|
| 174 |
-
|
| 175 |
-
# Reset stop flag and enable Stop button at start
|
| 176 |
-
stop_simulation = False
|
| 177 |
-
state.stop_button_disabled = False
|
| 178 |
-
|
| 179 |
-
plotter.clear()
|
| 180 |
-
current_mesh = None
|
| 181 |
-
state.error_message = ""
|
| 182 |
-
state.is_running = True
|
| 183 |
-
state.simulation_has_run = False
|
| 184 |
-
state.run_button_text = "Running"
|
| 185 |
-
ctrl.view_update()
|
| 186 |
-
|
| 187 |
-
nx, T = int(state.nx), float(state.T)
|
| 188 |
-
na, R = 1, 4
|
| 189 |
-
|
| 190 |
-
try:
|
| 191 |
-
if state.dist_type == "Delta":
|
| 192 |
-
initial_state = create_impulse_state_from_pos((nx, nx), (float(state.impulse_x), float(state.impulse_y)))
|
| 193 |
-
else:
|
| 194 |
-
initial_state = create_gaussian_state_from_pos((nx, nx), (float(state.mu_x), float(state.mu_y)), (float(state.sigma_x), float(state.sigma_y)))
|
| 195 |
-
except ValueError as e:
|
| 196 |
-
state.error_message = f"Initial State Error: {e}"
|
| 197 |
-
state.is_running = False
|
| 198 |
-
state.run_button_text = "Run Simulation"
|
| 199 |
-
state.stop_button_disabled = True
|
| 200 |
-
return
|
| 201 |
-
|
| 202 |
-
print("Running simulation...")
|
| 203 |
-
# Pass user-defined snapshot Δt; keep solver dt=0.1 inside run_sim
|
| 204 |
-
snapshot_dt = float(state.dt_user)
|
| 205 |
-
def _stop_check():
|
| 206 |
-
return stop_simulation
|
| 207 |
-
simulation_data, snapshot_times = run_sim(nx, na, R, initial_state, T, snapshot_dt=snapshot_dt, stop_check=_stop_check)
|
| 208 |
-
print("Simulation complete.")
|
| 209 |
-
|
| 210 |
-
if simulation_data.size > 0:
|
| 211 |
-
setup_surface_plot_data(simulation_data, nx)
|
| 212 |
-
state.simulation_has_run = True
|
| 213 |
-
state.run_button_text = "Successful!"
|
| 214 |
-
# Allow the view to use full area after run (remove strict square)
|
| 215 |
-
state.pyvista_view_style = ""
|
| 216 |
-
generate_plot()
|
| 217 |
-
else:
|
| 218 |
-
state.error_message = "Simulation produced no data. Check parameters (e.g., T > 0)."
|
| 219 |
-
state.run_button_text = "Run Simulation"
|
| 220 |
-
|
| 221 |
-
state.is_running = False
|
| 222 |
-
state.stop_button_disabled = True
|
| 223 |
-
|
| 224 |
-
def reset_to_defaults():
|
| 225 |
-
"""Reset all parameters to their default values"""
|
| 226 |
-
global simulation_data, current_mesh, data_frames, stop_simulation, snapshot_times
|
| 227 |
-
|
| 228 |
-
# Stop any running simulation
|
| 229 |
-
stop_simulation = True
|
| 230 |
-
|
| 231 |
-
# Reset global variables
|
| 232 |
-
simulation_data = None
|
| 233 |
-
current_mesh = None
|
| 234 |
-
data_frames = None
|
| 235 |
-
snapshot_times = None
|
| 236 |
-
|
| 237 |
-
# Reset state to default values
|
| 238 |
-
state.update({
|
| 239 |
-
"dist_type": None,
|
| 240 |
-
"impulse_x": 0.5,
|
| 241 |
-
"impulse_y": 0.5,
|
| 242 |
-
"peak_pair": "(0.5, 0.5)",
|
| 243 |
-
"mu_x": 0.5,
|
| 244 |
-
"mu_y": 0.5,
|
| 245 |
-
"sigma_x": 0.25,
|
| 246 |
-
"sigma_y": 0.15,
|
| 247 |
-
"mu_pair": "(0.5, 0.5)",
|
| 248 |
-
"sigma_pair": "(0.25, 0.15)",
|
| 249 |
-
"nx": None,
|
| 250 |
-
"T": 10.0,
|
| 251 |
-
"time_val": 0.0,
|
| 252 |
-
"output_type": "Surface Plot",
|
| 253 |
-
"surface_field": "Ez",
|
| 254 |
-
"timeseries_field": "Ez",
|
| 255 |
-
"timeseries_points": "(8, 8), (10, 8)",
|
| 256 |
-
"error_message": "",
|
| 257 |
-
"excitation_info_message": "",
|
| 258 |
-
"is_running": False,
|
| 259 |
-
"simulation_has_run": False,
|
| 260 |
-
"geometry_selection": None,
|
| 261 |
-
"coeff_permittivity": 1.0,
|
| 262 |
-
"coeff_permeability": 1.0,
|
| 263 |
-
"run_button_text": "Run Simulation",
|
| 264 |
-
"backend_type": "Simulator",
|
| 265 |
-
"selected_simulator": "IBM Qiskit simulator",
|
| 266 |
-
"selected_qpu": "IBM QPU",
|
| 267 |
-
"stop_button_disabled": True,
|
| 268 |
-
"export_format": "vtk",
|
| 269 |
-
"nx_slider_index": None,
|
| 270 |
-
"dt_user": 0.1,
|
| 271 |
-
"temporal_warning": "",
|
| 272 |
-
# Restore square aspect for initial preview
|
| 273 |
-
"pyvista_view_style": "aspect-ratio: 1 / 1; width: 100%;",
|
| 274 |
-
})
|
| 275 |
-
|
| 276 |
-
# Ensure stop flag is cleared for next run
|
| 277 |
-
stop_simulation = False
|
| 278 |
-
|
| 279 |
-
# Update the preview with default values
|
| 280 |
-
update_initial_state_preview()
|
| 281 |
-
print("Reset to default settings")
|
| 282 |
-
|
| 283 |
-
@state.change("peak_pair")
|
| 284 |
-
def sync_peak_pair(peak_pair, **kwargs):
|
| 285 |
-
"""Parse normalized pair (x, y) in [0,1] for Peak and update impulse_x/impulse_y."""
|
| 286 |
-
try:
|
| 287 |
-
m = re.match(r"\(\s*([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*\)", str(peak_pair))
|
| 288 |
-
if not m:
|
| 289 |
-
raise ValueError("Invalid format")
|
| 290 |
-
x = max(0.0, min(1.0, float(m.group(1))))
|
| 291 |
-
y = max(0.0, min(1.0, float(m.group(2))))
|
| 292 |
-
state.impulse_x = x
|
| 293 |
-
state.impulse_y = y
|
| 294 |
-
state.excitation_error_message = ""
|
| 295 |
-
except Exception:
|
| 296 |
-
state.excitation_error_message = "Invalid Peak. Use format (x, y) in [0,1]."
|
| 297 |
-
finally:
|
| 298 |
-
update_excitation_info_message()
|
| 299 |
-
|
| 300 |
-
@state.change("mu_pair")
|
| 301 |
-
def sync_mu_pair(mu_pair, **kwargs):
|
| 302 |
-
"""Parse normalized pair (x, y) in [0,1] for Mu and update mu_x/mu_y."""
|
| 303 |
-
try:
|
| 304 |
-
m = re.match(r"\(\s*([-+]?[0-9]*\.?[0-9]+)\s*,\s*([-+]?[0-9]*\.?[0-9]+)\s*\)", str(mu_pair))
|
| 305 |
-
if not m:
|
| 306 |
-
raise ValueError("Invalid format")
|
| 307 |
-
x = max(0.0, min(1.0, float(m.group(1))))
|
| 308 |
-
y = max(0.0, min(1.0, float(m.group(2))))
|
| 309 |
-
state.mu_x = x
|
| 310 |
-
state.mu_y = y
|
| 311 |
-
state.excitation_error_message = ""
|
| 312 |
-
except Exception:
|
| 313 |
-
state.excitation_error_message = "Invalid Mu. Use format (x, y) in [0,1]."
|
| 314 |
-
finally:
|
| 315 |
-
update_excitation_info_message()
|
| 316 |
-
|
| 317 |
-
def stop_simulation_handler():
|
| 318 |
-
"""Stop the currently running simulation"""
|
| 319 |
-
global stop_simulation
|
| 320 |
-
stop_simulation = True
|
| 321 |
-
state.stop_button_disabled = True
|
| 322 |
-
print("Stopping simulation...")
|
| 323 |
-
|
| 324 |
-
def generate_plot():
|
| 325 |
-
global current_mesh
|
| 326 |
-
if not state.simulation_has_run: return
|
| 327 |
-
|
| 328 |
-
plotter.clear()
|
| 329 |
-
try: plotter.disable_picking()
|
| 330 |
-
except: pass
|
| 331 |
-
|
| 332 |
-
nx, T = int(state.nx), float(state.T)
|
| 333 |
-
|
| 334 |
-
if state.output_type == "Surface Plot":
|
| 335 |
-
redraw_surface_plot()
|
| 336 |
-
else: # Time Series
|
| 337 |
-
try:
|
| 338 |
-
points_str = state.timeseries_points
|
| 339 |
-
positions = [tuple(map(int, match)) for match in re.findall(r'\((\d+)\s*,\s*(\d+)\)', points_str)]
|
| 340 |
-
if not positions and points_str.strip(): raise ValueError("No valid points found.")
|
| 341 |
-
chart = get_time_series_chart(simulation_data, state.timeseries_field, positions, nx, snapshot_times)
|
| 342 |
-
plotter.add_chart(chart)
|
| 343 |
-
plotter.view_xy() # Set a 2D view for the chart
|
| 344 |
-
except Exception as e:
|
| 345 |
-
state.error_message = f"Plotting Error: {e}"
|
| 346 |
-
|
| 347 |
-
ctrl.view_update()
|
| 348 |
-
|
| 349 |
-
def redraw_surface_plot():
|
| 350 |
-
global current_mesh
|
| 351 |
-
plotter.clear()
|
| 352 |
-
field = state.surface_field
|
| 353 |
-
if data_frames is None or not data_frames.get(field): return
|
| 354 |
-
if snapshot_times is None or len(snapshot_times) == 0: return
|
| 355 |
-
|
| 356 |
-
# Find nearest snapshot index to requested time and clamp to available frames
|
| 357 |
-
req_t = float(state.time_val)
|
| 358 |
-
times = np.asarray(snapshot_times)
|
| 359 |
-
idx = int(np.argmin(np.abs(times - req_t)))
|
| 360 |
-
max_idx = len(data_frames[field]) - 1
|
| 361 |
-
idx = max(0, min(idx, max_idx))
|
| 362 |
-
|
| 363 |
-
z_data = data_frames[field][idx]
|
| 364 |
-
points = np.c_[X_grids[field].ravel(), Y_grids[field].ravel(), z_data.ravel() * z_scale]
|
| 365 |
-
poly = pv.PolyData(points)
|
| 366 |
-
mesh = poly.delaunay_2d()
|
| 367 |
-
mesh['scalars'] = z_data.ravel()
|
| 368 |
-
current_mesh = mesh
|
| 369 |
-
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)
|
| 370 |
-
plotter.add_scalar_bar(title=f"{field} Amplitude")
|
| 371 |
-
try:
|
| 372 |
-
plotter.disable_picking()
|
| 373 |
-
except Exception:
|
| 374 |
-
pass
|
| 375 |
-
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 376 |
-
plotter.add_axes()
|
| 377 |
-
plotter.view_isometric()
|
| 378 |
-
try:
|
| 379 |
-
plotter.camera.parallel_projection = True
|
| 380 |
-
except Exception:
|
| 381 |
-
pass
|
| 382 |
-
ctrl.view_update()
|
| 383 |
-
|
| 384 |
-
# Helper: add a dotted unit grid (0..1) overlay in light Synopsys purple
|
| 385 |
-
def _add_dotted_unit_grid(plotter, ticks=(0.0, 0.25, 0.5, 0.75, 1.0), segments=48, gap_ratio=0.4, color="#AE8BD8", line_width=0.2):
|
| 386 |
-
try:
|
| 387 |
-
step = 1.0 / float(max(segments, 1))
|
| 388 |
-
seg_len = step * float(max(0.0, min(1.0, 1.0 - gap_ratio)))
|
| 389 |
-
pts = []
|
| 390 |
-
lines = []
|
| 391 |
-
# Horizontal dotted lines at given y=tick
|
| 392 |
-
for y in ticks:
|
| 393 |
-
pos = 0.0
|
| 394 |
-
while pos < 1.0 - 1e-9:
|
| 395 |
-
y0, y1 = pos, min(pos + seg_len, 1.0)
|
| 396 |
-
pts.extend([(0.0, y, 0.0), (1.0, y, 0.0)]) # end points along x (we'll segment via multiple x positions)
|
| 397 |
-
# Replace with segmented along X
|
| 398 |
-
pts[-2] = (pos, y, 0.0)
|
| 399 |
-
pts[-1] = (y1 if seg_len > 0 else pos, y, 0.0)
|
| 400 |
-
i0 = len(pts) - 2
|
| 401 |
-
lines.extend([2, i0, i0 + 1])
|
| 402 |
-
pos += step
|
| 403 |
-
# Vertical dotted lines at given x=tick
|
| 404 |
-
for x in ticks:
|
| 405 |
-
pos = 0.0
|
| 406 |
-
while pos < 1.0 - 1e-9:
|
| 407 |
-
y0, y1 = pos, min(pos + seg_len, 1.0)
|
| 408 |
-
pts.extend([(x, pos, 0.0), (x, y1 if seg_len > 0 else pos, 0.0)])
|
| 409 |
-
i0 = len(pts) - 2
|
| 410 |
-
lines.extend([2, i0, i0 + 1])
|
| 411 |
-
pos += step
|
| 412 |
-
if pts and lines:
|
| 413 |
-
poly = pv.PolyData(np.array(pts))
|
| 414 |
-
poly.lines = np.array(lines)
|
| 415 |
-
plotter.add_mesh(poly, color=color, line_width=line_width, name="dotted_unit_grid", pickable=False)
|
| 416 |
-
except Exception:
|
| 417 |
-
pass
|
| 418 |
-
|
| 419 |
-
# Scaled dotted unit grid overlay for integer-coordinate previews (Delta/Gaussian)
|
| 420 |
-
def _add_dotted_unit_grid_scaled(plotter, denom, ticks=(0.0, 0.25, 0.5, 0.75, 1.0), segments=48, gap_ratio=0.6, color="#AE8BD8", line_width=1.0, name="dotted_unit_grid_preview"):
|
| 421 |
-
"""Overlay a 0–1 dotted grid scaled to [0, denom] on the XY plane without changing mesh coordinates."""
|
| 422 |
-
try:
|
| 423 |
-
step = 1.0 / float(max(segments, 1))
|
| 424 |
-
seg_len = step * float(max(0.0, min(1.0, 1.0 - gap_ratio)))
|
| 425 |
-
# Set a z slightly below mesh to avoid z-fighting
|
| 426 |
-
try:
|
| 427 |
-
z0 = float(current_mesh.points[:, 2].min()) - 1e-6 if current_mesh is not None else 0.0
|
| 428 |
-
except Exception:
|
| 429 |
-
z0 = 0.0
|
| 430 |
-
pts, lines = [], []
|
| 431 |
-
# Vertical lines at x = t * denom
|
| 432 |
-
for t in ticks:
|
| 433 |
-
x = float(t) * float(denom)
|
| 434 |
-
pos = 0.0
|
| 435 |
-
while pos < 1.0 - 1e-9:
|
| 436 |
-
y0 = pos * denom
|
| 437 |
-
y1 = min(pos + seg_len, 1.0) * denom
|
| 438 |
-
pts.extend([(x, y0, z0), (x, y1, z0)])
|
| 439 |
-
i0 = len(pts) - 2
|
| 440 |
-
lines.extend([2, i0, i0 + 1])
|
| 441 |
-
pos += step
|
| 442 |
-
# Horizontal lines at y = t * denom
|
| 443 |
-
for t in ticks:
|
| 444 |
-
y = float(t) * float(denom)
|
| 445 |
-
pos = 0.0
|
| 446 |
-
while pos < 1.0 - 1e-9:
|
| 447 |
-
x0 = pos * denom
|
| 448 |
-
x1 = min(pos + seg_len, 1.0) * denom
|
| 449 |
-
pts.extend([(x0, y, z0), (x1, y, z0)])
|
| 450 |
-
i0 = len(pts) - 2
|
| 451 |
-
lines.extend([2, i0, i0 + 1])
|
| 452 |
-
pos += step
|
| 453 |
-
try:
|
| 454 |
-
plotter.remove_actor(name)
|
| 455 |
-
except Exception:
|
| 456 |
-
pass
|
| 457 |
-
if pts and lines:
|
| 458 |
-
poly = pv.PolyData(np.array(pts))
|
| 459 |
-
poly.lines = np.array(lines)
|
| 460 |
-
plotter.add_mesh(poly, color=color, line_width=line_width, name=name, pickable=False)
|
| 461 |
-
except Exception:
|
| 462 |
-
pass
|
| 463 |
-
|
| 464 |
-
# --- Plain Square Domain Preview ---
|
| 465 |
-
def update_geometry_preview():
|
| 466 |
-
"""Render a flat square mesh (Z=0) with edges for 'Square Domain'."""
|
| 467 |
-
global current_mesh
|
| 468 |
-
if state.is_running or state.simulation_has_run:
|
| 469 |
-
return
|
| 470 |
-
plotter.clear()
|
| 471 |
-
nx = int(state.nx) if state.nx is not None else 32
|
| 472 |
-
# Build normalized coordinates in [0, 1]
|
| 473 |
-
denom = max(nx - 1, 1)
|
| 474 |
-
x, y = np.arange(nx) / denom, np.arange(nx) / denom
|
| 475 |
-
X, Y = np.meshgrid(x, y)
|
| 476 |
-
Z = np.zeros_like(X, dtype=float)
|
| 477 |
-
points = np.c_[X.ravel(), Y.ravel(), Z.ravel()]
|
| 478 |
-
poly = pv.PolyData(points)
|
| 479 |
-
mesh = poly.delaunay_2d()
|
| 480 |
-
mesh['scalars'] = Z.ravel()
|
| 481 |
-
current_mesh = mesh
|
| 482 |
-
plotter.add_mesh(
|
| 483 |
-
mesh,
|
| 484 |
-
color="#FFDAB9", # Peach Puff to match Square Metallic Body domain
|
| 485 |
-
show_scalar_bar=False,
|
| 486 |
-
show_edges=False,
|
| 487 |
-
edge_color='grey',
|
| 488 |
-
line_width=0.5,
|
| 489 |
-
)
|
| 490 |
-
try:
|
| 491 |
-
plotter.disable_picking()
|
| 492 |
-
except Exception:
|
| 493 |
-
pass
|
| 494 |
-
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 495 |
-
plotter.add_axes()
|
| 496 |
-
# Axes scaled 0..1
|
| 497 |
-
plotter.show_grid(bounds=(0.0, 1.0, 0.0, 1.0, 0.0, 0.0), xtitle="x (0–1)", ytitle="y (0–1)", ztitle=" ", color="#AE8BD8")
|
| 498 |
-
_add_dotted_unit_grid(plotter, ticks=(0.0, 0.25, 0.5, 0.75, 1.0), segments=48, gap_ratio=0.6, color="#AE8BD8", line_width=1)
|
| 499 |
-
plotter.view_isometric()
|
| 500 |
-
try:
|
| 501 |
-
plotter.camera.parallel_projection = True
|
| 502 |
-
except Exception:
|
| 503 |
-
pass
|
| 504 |
-
ctrl.view_update()
|
| 505 |
-
|
| 506 |
-
# --- Plain Square Domain (Hole) Preview ---
|
| 507 |
-
def update_geometry_hole_preview():
|
| 508 |
-
"""Render a flat square mesh with a square hole defined by center (cx, cy) and size a."""
|
| 509 |
-
global current_mesh
|
| 510 |
-
if state.is_running or state.simulation_has_run:
|
| 511 |
-
return
|
| 512 |
-
plotter.clear()
|
| 513 |
-
nx = int(state.nx) if state.nx is not None else 32
|
| 514 |
-
denom = max(nx - 1, 1)
|
| 515 |
-
# Normalized grid in [0,1]
|
| 516 |
-
x, y = np.arange(nx) / denom, np.arange(nx) / denom
|
| 517 |
-
X, Y = np.meshgrid(x, y)
|
| 518 |
-
Z = np.zeros_like(X, dtype=float)
|
| 519 |
-
points = np.c_[X.ravel(), Y.ravel(), Z.ravel()]
|
| 520 |
-
poly = pv.PolyData(points)
|
| 521 |
-
mesh = poly.delaunay_2d()
|
| 522 |
-
|
| 523 |
-
# Read user inputs
|
| 524 |
-
try:
|
| 525 |
-
a = float(state.hole_size_edge)
|
| 526 |
-
cx = float(state.hole_center_x)
|
| 527 |
-
cy = float(state.hole_center_y)
|
| 528 |
-
except Exception:
|
| 529 |
-
a, cx, cy = 0.2, 0.5, 0.5 # fallback
|
| 530 |
-
|
| 531 |
-
mode_snap = bool(state.hole_snap)
|
| 532 |
-
edges = _compute_hole_edges(nx, cx, cy, a, snap=mode_snap)
|
| 533 |
-
if edges is not None:
|
| 534 |
-
xL, xR, yB, yT = edges
|
| 535 |
-
# Open rectangle: remove cells with centers strictly inside (boundaries excluded)
|
| 536 |
-
centers = mesh.cell_centers().points
|
| 537 |
-
in_x = (centers[:, 0] > xL) & (centers[:, 0] < xR)
|
| 538 |
-
in_y = (centers[:, 1] > yB) & (centers[:, 1] < yT)
|
| 539 |
-
hole_mask = in_x & in_y
|
| 540 |
-
if hole_mask.any():
|
| 541 |
-
mesh = mesh.remove_cells(np.where(hole_mask)[0])
|
| 542 |
-
mesh.clean(inplace=True)
|
| 543 |
-
|
| 544 |
-
# Color domain peach and overlay a black filled rectangle for the hole
|
| 545 |
-
current_mesh = mesh
|
| 546 |
-
try:
|
| 547 |
-
plotter.remove_actor("hole_overlay")
|
| 548 |
-
except Exception:
|
| 549 |
-
pass
|
| 550 |
-
|
| 551 |
-
# Draw domain in peach color so it doesn't blend with background
|
| 552 |
-
plotter.add_mesh(
|
| 553 |
-
mesh,
|
| 554 |
-
color="#FFDAB9", # Peach Puff
|
| 555 |
-
show_scalar_bar=False,
|
| 556 |
-
show_edges=False,
|
| 557 |
-
edge_color='grey',
|
| 558 |
-
line_width=0.5,
|
| 559 |
-
)
|
| 560 |
-
|
| 561 |
-
# If edges are valid, add a black filled rectangle for the hole area
|
| 562 |
-
if edges is not None:
|
| 563 |
-
xL, xR, yB, yT = edges
|
| 564 |
-
z0 = -1e-6 # Slightly below to avoid z-fighting at boundaries
|
| 565 |
-
rect_pts = np.array([
|
| 566 |
-
[xL, yB, z0],
|
| 567 |
-
[xR, yB, z0],
|
| 568 |
-
[xR, yT, z0],
|
| 569 |
-
[xL, yT, z0],
|
| 570 |
-
], dtype=float)
|
| 571 |
-
rect_faces = np.hstack([[4, 0, 1, 2, 3]])
|
| 572 |
-
rect = pv.PolyData(rect_pts, rect_faces)
|
| 573 |
-
plotter.add_mesh(rect, color="black", name="hole_overlay", pickable=False)
|
| 574 |
-
|
| 575 |
-
# Grid and view setup
|
| 576 |
-
plotter.show_grid(bounds=(0.0, 1.0, 0.0, 1.0, 0.0, 0.0), xtitle="x (0–1)", ytitle="y (0–1)", ztitle=" ", color="#AE8BD8")
|
| 577 |
-
_add_dotted_unit_grid(plotter, ticks=(0.0, 0.25, 0.5, 0.75, 1.0), segments=48, gap_ratio=0.6, color="#AE8BD8", line_width=1)
|
| 578 |
-
try:
|
| 579 |
-
plotter.disable_picking()
|
| 580 |
-
except Exception:
|
| 581 |
-
pass
|
| 582 |
-
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 583 |
-
plotter.add_axes()
|
| 584 |
-
plotter.view_isometric()
|
| 585 |
-
try:
|
| 586 |
-
plotter.camera.parallel_projection = True
|
| 587 |
-
except Exception:
|
| 588 |
-
pass
|
| 589 |
-
ctrl.view_update()
|
| 590 |
-
|
| 591 |
-
# Helper: map normalized [0,1] to nearest node index on an nx×ny grid
|
| 592 |
-
def _nearest_node_index(x: float, y: float, nx: int, ny: int | None = None):
|
| 593 |
-
ny = ny or nx
|
| 594 |
-
i = int(round(float(x) * (nx - 1)))
|
| 595 |
-
j = int(round(float(y) * (ny - 1)))
|
| 596 |
-
i = max(0, min(nx - 1, i))
|
| 597 |
-
j = max(0, min(ny - 1, j))
|
| 598 |
-
return i, j
|
| 599 |
-
|
| 600 |
-
def update_initial_state_preview():
|
| 601 |
-
global current_mesh
|
| 602 |
-
# Don't render any preview while running
|
| 603 |
-
if state.is_running:
|
| 604 |
-
plotter.clear(); ctrl.view_update(); return
|
| 605 |
-
# If no geometry selected, clear and stop
|
| 606 |
-
if not state.geometry_selection:
|
| 607 |
-
plotter.clear(); ctrl.view_update(); return
|
| 608 |
-
# Geometry-only previews before initial state selection
|
| 609 |
-
if not state.simulation_has_run and not state.dist_type:
|
| 610 |
-
if state.geometry_selection == "Square Domain":
|
| 611 |
-
update_geometry_preview(); return
|
| 612 |
-
if state.geometry_selection == "Square Metallic Body":
|
| 613 |
-
update_geometry_hole_preview(); return
|
| 614 |
-
|
| 615 |
-
plotter.clear()
|
| 616 |
-
state.error_message = ""
|
| 617 |
-
# Default to a high-resolution 128x128 preview grid
|
| 618 |
-
preview_n = 128
|
| 619 |
-
nx_sel = state.nx
|
| 620 |
-
# Show grid edges only when a mesh size is selected
|
| 621 |
-
show_grid_edges = nx_sel is not None
|
| 622 |
-
|
| 623 |
-
try:
|
| 624 |
-
grid_n = int(nx_sel) if nx_sel is not None else preview_n
|
| 625 |
-
|
| 626 |
-
if state.dist_type == "Delta":
|
| 627 |
-
ix, iy = _nearest_node_index(float(state.impulse_x), float(state.impulse_y), grid_n)
|
| 628 |
-
full_state = create_impulse_state((grid_n, grid_n), (ix, iy))
|
| 629 |
-
elif state.dist_type == "Gaussian":
|
| 630 |
-
ix, iy = _nearest_node_index(float(state.mu_x), float(state.mu_y), grid_n)
|
| 631 |
-
sx = max(float(state.sigma_x) * (grid_n - 1), 1e-9)
|
| 632 |
-
sy = max(float(state.sigma_y) * (grid_n - 1), 1e-9)
|
| 633 |
-
full_state = create_gaussian_state((grid_n, grid_n), (ix, iy), (sx, sy))
|
| 634 |
-
else:
|
| 635 |
-
return
|
| 636 |
-
|
| 637 |
-
# Build preview mesh using the correct grid size
|
| 638 |
-
initial_grid = full_state[: grid_n * grid_n].reshape(grid_n, grid_n)
|
| 639 |
-
denom = float(max(grid_n - 1, 1))
|
| 640 |
-
x, y = np.arange(grid_n) / denom, np.arange(grid_n) / denom
|
| 641 |
-
X, Y = np.meshgrid(x, y)
|
| 642 |
-
max_abs = float(np.max(np.abs(initial_grid))) if initial_grid.size else 1.0
|
| 643 |
-
if max_abs < 1e-12:
|
| 644 |
-
max_abs = 1.0
|
| 645 |
-
height_scale = 0.15
|
| 646 |
-
Z = (initial_grid / max_abs) * height_scale
|
| 647 |
-
mesh = pv.StructuredGrid()
|
| 648 |
-
mesh.points = np.c_[X.ravel(), Y.ravel(), Z.ravel()]
|
| 649 |
-
mesh.dimensions = (grid_n, grid_n, 1)
|
| 650 |
-
mesh['scalars'] = initial_grid.ravel()
|
| 651 |
-
current_mesh = mesh
|
| 652 |
-
|
| 653 |
-
plotter.add_mesh(
|
| 654 |
-
mesh,
|
| 655 |
-
scalars='scalars',
|
| 656 |
-
cmap="Blues",
|
| 657 |
-
show_scalar_bar=False,
|
| 658 |
-
show_edges=show_grid_edges,
|
| 659 |
-
edge_color='grey',
|
| 660 |
-
line_width=0.5,
|
| 661 |
-
)
|
| 662 |
-
# No scalar bar, axes, or grid overlays in excitation preview; picking only
|
| 663 |
-
try:
|
| 664 |
-
plotter.disable_picking()
|
| 665 |
-
except Exception:
|
| 666 |
-
pass
|
| 667 |
-
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 668 |
-
plotter.view_isometric()
|
| 669 |
-
try:
|
| 670 |
-
plotter.camera.parallel_projection = True
|
| 671 |
-
except Exception:
|
| 672 |
-
pass
|
| 673 |
-
ctrl.view_update()
|
| 674 |
-
|
| 675 |
-
except ValueError as e:
|
| 676 |
-
state.error_message = f"Parameter Error: {e}"
|
| 677 |
-
except Exception as e:
|
| 678 |
-
state.error_message = f"An unexpected error occurred: {e}"
|
| 679 |
-
|
| 680 |
-
@state.change("geometry_selection")
|
| 681 |
-
def handle_geometry_add(geometry_selection, **kwargs):
|
| 682 |
-
# Normalize unselect options to None
|
| 683 |
-
if geometry_selection in (None, "", "None"):
|
| 684 |
-
state.geometry_selection = None
|
| 685 |
-
update_initial_state_preview()
|
| 686 |
-
return
|
| 687 |
-
if (geometry_selection == "Add"):
|
| 688 |
-
state.show_upload_dialog = True
|
| 689 |
-
state.geometry_selection = None
|
| 690 |
-
return
|
| 691 |
-
# Update preview on any geometry change (e.g., show Square Domain flat mesh)
|
| 692 |
-
update_initial_state_preview()
|
| 693 |
-
|
| 694 |
-
@state.change("uploaded_file_info")
|
| 695 |
-
def handle_file_upload(uploaded_file_info, **kwargs):
|
| 696 |
-
if uploaded_file_info:
|
| 697 |
-
file_name = uploaded_file_info.get("name", "unknown file")
|
| 698 |
-
print(f"File selected (dummy upload): {file_name}")
|
| 699 |
-
state.show_upload_dialog = False
|
| 700 |
-
state.upload_status_message = f"File '{file_name}' uploaded."
|
| 701 |
-
state.show_upload_status = True
|
| 702 |
-
|
| 703 |
-
def update_excitation_info_message():
|
| 704 |
-
"""Calculates and displays the coordinate snapping message."""
|
| 705 |
-
if state.nx is None or state.dist_type is None:
|
| 706 |
-
state.excitation_info_message = ""
|
| 707 |
-
return
|
| 708 |
-
|
| 709 |
-
try:
|
| 710 |
-
nx = int(state.nx)
|
| 711 |
-
denom = float(max(nx - 1, 1))
|
| 712 |
-
|
| 713 |
-
if state.dist_type == "Delta":
|
| 714 |
-
x_in, y_in = float(state.impulse_x), float(state.impulse_y)
|
| 715 |
-
elif state.dist_type == "Gaussian":
|
| 716 |
-
x_in, y_in = float(state.mu_x), float(state.mu_y)
|
| 717 |
-
else:
|
| 718 |
-
state.excitation_info_message = ""
|
| 719 |
-
return
|
| 720 |
-
|
| 721 |
-
ix, iy = _nearest_node_index(x_in, y_in, nx)
|
| 722 |
-
x_snapped, y_snapped = ix / denom, iy / denom
|
| 723 |
-
|
| 724 |
-
if abs(x_in - x_snapped) > 1e-9 or abs(y_in - y_snapped) > 1e-9:
|
| 725 |
-
state.excitation_info_message = f"Input ({x_in:.3f}, {y_in:.3f}) adjusted to nearest grid point ({x_snapped:.3f}, {y_snapped:.3f})."
|
| 726 |
-
else:
|
| 727 |
-
state.excitation_info_message = ""
|
| 728 |
-
except Exception:
|
| 729 |
-
state.excitation_info_message = ""
|
| 730 |
-
|
| 731 |
-
@state.change("nx_slider_index")
|
| 732 |
-
def on_slider_index_change(nx_slider_index, **kwargs):
|
| 733 |
-
if nx_slider_index is None:
|
| 734 |
-
state.nx = None
|
| 735 |
-
else:
|
| 736 |
-
try:
|
| 737 |
-
state.nx = int(GRID_SIZES[int(nx_slider_index)])
|
| 738 |
-
except Exception:
|
| 739 |
-
state.nx = None
|
| 740 |
-
update_excitation_info_message()
|
| 741 |
-
|
| 742 |
-
@state.change("nx", "T", "dist_type", "impulse_x", "impulse_y", "mu_x", "mu_y", "sigma_x", "sigma_y", "coeff_permittivity", "coeff_permeability")
|
| 743 |
-
def on_input_parameter_change(**kwargs):
|
| 744 |
-
# Do nothing while running
|
| 745 |
-
if state.is_running:
|
| 746 |
-
return
|
| 747 |
-
|
| 748 |
-
update_excitation_info_message()
|
| 749 |
-
|
| 750 |
-
changed_keys = set(kwargs.keys())
|
| 751 |
-
|
| 752 |
-
# If a simulation has already run, keep current results and only indicate re-run is needed
|
| 753 |
-
if state.simulation_has_run:
|
| 754 |
-
state.run_button_text = "Re-run Simulation"
|
| 755 |
-
return
|
| 756 |
-
|
| 757 |
-
# Before a run, update the initial preview only when relevant preview params changed
|
| 758 |
-
preview_params = {"nx", "dist_type", "impulse_x", "impulse_y", "mu_x", "mu_y", "sigma_x", "sigma_y"}
|
| 759 |
-
if changed_keys & preview_params:
|
| 760 |
-
update_initial_state_preview()
|
| 761 |
-
|
| 762 |
-
@state.change("output_type", "timeseries_field", "timeseries_points")
|
| 763 |
-
def on_output_config_change(**kwargs):
|
| 764 |
-
if state.simulation_has_run:
|
| 765 |
-
generate_plot()
|
| 766 |
-
|
| 767 |
-
@state.change("surface_field")
|
| 768 |
-
def on_surface_field_change(surface_field, **kwargs):
|
| 769 |
-
if state.simulation_has_run and state.output_type == "Surface Plot":
|
| 770 |
-
redraw_surface_plot()
|
| 771 |
-
|
| 772 |
-
@state.change("time_val")
|
| 773 |
-
def on_time_change(time_val, **kwargs):
|
| 774 |
-
if not state.simulation_has_run or state.output_type != "Surface Plot" or current_mesh is None or data_frames is None:
|
| 775 |
-
return
|
| 776 |
-
if snapshot_times is None or len(snapshot_times) == 0:
|
| 777 |
-
return
|
| 778 |
-
field = state.surface_field
|
| 779 |
-
# Find nearest snapshot index to requested time and clamp to available frames
|
| 780 |
-
times = np.asarray(snapshot_times)
|
| 781 |
-
idx = int(np.argmin(np.abs(times - float(time_val))))
|
| 782 |
-
max_idx = len(data_frames[field]) - 1
|
| 783 |
-
idx = max(0, min(idx, max_idx))
|
| 784 |
-
z_data = data_frames[field][idx]
|
| 785 |
-
if current_mesh.n_points == z_data.size:
|
| 786 |
-
current_mesh.points[:, 2] = z_data.ravel() * z_scale
|
| 787 |
-
current_mesh['scalars'] = z_data.ravel()
|
| 788 |
-
ctrl.view_update()
|
| 789 |
-
else:
|
| 790 |
-
redraw_surface_plot()
|
| 791 |
-
|
| 792 |
-
def update_value_display(point):
|
| 793 |
-
if current_mesh is None:
|
| 794 |
-
return
|
| 795 |
-
try:
|
| 796 |
-
plotter.remove_actor("value_text")
|
| 797 |
-
except Exception:
|
| 798 |
-
pass
|
| 799 |
-
|
| 800 |
-
closest_id = current_mesh.find_closest_point(point)
|
| 801 |
-
if closest_id == -1:
|
| 802 |
-
return
|
| 803 |
-
|
| 804 |
-
# Sample value and coordinates at closest vertex
|
| 805 |
-
value = current_mesh['scalars'][closest_id] if 'scalars' in current_mesh.array_names else 0.0
|
| 806 |
-
px, py, pz = current_mesh.points[closest_id]
|
| 807 |
-
px = float(px); py = float(py)
|
| 808 |
-
|
| 809 |
-
# Determine if current mesh is on unit square [0,1] (initial preview/geometry) or integer grid (output plots)
|
| 810 |
-
xmin, xmax, ymin, ymax, _, _ = current_mesh.bounds
|
| 811 |
-
is_unit_square = (xmax <= 1.00001 and ymax <= 1.00001)
|
| 812 |
-
|
| 813 |
-
if not state.simulation_has_run and is_unit_square:
|
| 814 |
-
# Disable updating inputs based on point picking
|
| 815 |
-
text = f"Position: ({px:.3f}, {py:.3f})\nValue: {value:.3e}"
|
| 816 |
-
else:
|
| 817 |
-
# Output configuration or integer-grid context: keep grid indices visible like app.py
|
| 818 |
-
nx_val = int(state.nx)
|
| 819 |
-
denom = max(float(nx_val - 1), 1.0)
|
| 820 |
-
if is_unit_square:
|
| 821 |
-
ix = int(round(px * denom)); iy = int(round(py * denom))
|
| 822 |
-
x_code = max(0.0, min(1.0, px)); y_code = max(0.0, min(1.0, py))
|
| 823 |
-
else:
|
| 824 |
-
ix = int(round(px)); iy = int(round(py))
|
| 825 |
-
x_code = max(0.0, min(1.0, px / denom)); y_code = max(0.0, min(1.0, py / denom))
|
| 826 |
-
ix = max(0, min(ix, nx_val - 1)); iy = max(0, min(iy, nx_val - 1))
|
| 827 |
-
if state.simulation_has_run:
|
| 828 |
-
time = float(state.time_val)
|
| 829 |
-
text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nTime: {time:.2f}s\nValue: {value:.3e}"
|
| 830 |
-
else:
|
| 831 |
-
text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nValue: {value:.3e}"
|
| 832 |
-
|
| 833 |
-
plotter.add_text(text, name="value_text", position="lower_left", color="black", font_size=10)
|
| 834 |
-
ctrl.view_update()
|
| 835 |
-
|
| 836 |
try:
|
| 837 |
-
|
|
|
|
| 838 |
except Exception:
|
| 839 |
-
|
| 840 |
-
plotter.enable_point_picking(callback=update_value_display, show_message=False)
|
| 841 |
-
|
| 842 |
-
def export_vtk():
|
| 843 |
-
"""Export current surface mesh to user's Downloads as .vtp and notify via snackbar."""
|
| 844 |
-
global current_mesh
|
| 845 |
-
if current_mesh is None:
|
| 846 |
-
state.export_status_message = "No mesh to export."
|
| 847 |
-
state.show_export_status = True
|
| 848 |
-
return
|
| 849 |
-
try:
|
| 850 |
-
dl_dir = os.path.join(os.path.expanduser("~"), "Downloads")
|
| 851 |
-
os.makedirs(dl_dir, exist_ok=True)
|
| 852 |
-
field = state.surface_field or "Ez"
|
| 853 |
-
nx = int(state.nx)
|
| 854 |
-
suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 855 |
-
path = os.path.join(dl_dir, f"surface_{field}_nx{nx}_{suffix}.vtp")
|
| 856 |
-
current_mesh.save(path)
|
| 857 |
-
state.export_status_message = f"Exported VTK to {path}"
|
| 858 |
-
except Exception as e:
|
| 859 |
-
state.export_status_message = f"Export failed: {e}"
|
| 860 |
-
state.show_export_status = True
|
| 861 |
-
|
| 862 |
-
def export_vtk_all_frames():
|
| 863 |
-
"""Export a .vtp file for each time frame of the selected component into a timestamped folder in Downloads."""
|
| 864 |
-
global data_frames, X_grids, z_scale, snapshot_times
|
| 865 |
-
try:
|
| 866 |
-
if not state.simulation_has_run:
|
| 867 |
-
raise ValueError("Run a simulation before exporting all frames.")
|
| 868 |
-
field = state.surface_field or "Ez"
|
| 869 |
-
frames = data_frames.get(field)
|
| 870 |
-
if not frames:
|
| 871 |
-
raise ValueError(f"No frames available for {field}.")
|
| 872 |
-
if snapshot_times is None:
|
| 873 |
-
raise ValueError("Snapshot times are unavailable.")
|
| 874 |
-
|
| 875 |
-
dl_dir = os.path.join(os.path.expanduser("~"), "Downloads")
|
| 876 |
-
suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 877 |
-
nx = int(state.nx)
|
| 878 |
-
out_dir = os.path.join(dl_dir, f"vtk_sequence_{field}_nx{nx}_{suffix}")
|
| 879 |
-
os.makedirs(out_dir, exist_ok=True)
|
| 880 |
-
|
| 881 |
-
times = np.asarray(snapshot_times)
|
| 882 |
-
for i, (z_data, t) in enumerate(zip(frames, times)):
|
| 883 |
-
points = np.c_[X_grids[field].ravel(), Y_grids[field].ravel(), z_data.ravel() * z_scale]
|
| 884 |
-
poly = pv.PolyData(points)
|
| 885 |
-
mesh = poly.delaunay_2d()
|
| 886 |
-
mesh["scalars"] = z_data.ravel()
|
| 887 |
-
fname = f"{field}_frame_{i:04d}_t{t:.3f}s.vtp"
|
| 888 |
-
mesh.save(os.path.join(out_dir, fname))
|
| 889 |
-
|
| 890 |
-
state.export_status_message = f"Exported {len(frames)} frames to {out_dir}"
|
| 891 |
-
except Exception as e:
|
| 892 |
-
state.export_status_message = f"Export failed: {e}"
|
| 893 |
-
finally:
|
| 894 |
-
state.show_export_status = True
|
| 895 |
-
|
| 896 |
-
def export_mp4():
|
| 897 |
-
"""Export the surface plot time slider animation to MP4 using a dedicated off-screen plotter."""
|
| 898 |
-
global data_frames
|
| 899 |
-
try:
|
| 900 |
-
if not state.simulation_has_run:
|
| 901 |
-
raise ValueError("Run a simulation before exporting MP4.")
|
| 902 |
-
field = state.surface_field or "Ez"
|
| 903 |
-
frames = data_frames.get(field)
|
| 904 |
-
if not frames:
|
| 905 |
-
raise ValueError(f"No frames available for {field}.")
|
| 906 |
-
if len(frames) < 2:
|
| 907 |
-
raise ValueError("Only one frame available; increase T or simulation steps.")
|
| 908 |
-
|
| 909 |
-
# Output path
|
| 910 |
-
dl_dir = os.path.join(os.path.expanduser("~"), "Downloads")
|
| 911 |
-
os.makedirs(dl_dir, exist_ok=True)
|
| 912 |
-
nx = int(state.nx)
|
| 913 |
-
suffix = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 914 |
-
path = os.path.join(dl_dir, f"surface_anim_{field}_nx{nx}_{suffix}.mp4")
|
| 915 |
-
|
| 916 |
-
# Build with a dedicated off-screen plotter at a macro-block friendly size
|
| 917 |
-
movie_plotter = pv.Plotter(off_screen=True, window_size=(1280, 720))
|
| 918 |
-
|
| 919 |
-
# Initial mesh from first frame
|
| 920 |
-
X = X_grids[field]
|
| 921 |
-
Y = Y_grids[field]
|
| 922 |
-
first = frames[0]
|
| 923 |
-
points = np.c_[X.ravel(), Y.ravel(), first.ravel() * z_scale]
|
| 924 |
-
poly = pv.PolyData(points)
|
| 925 |
-
mesh = poly.delaunay_2d()
|
| 926 |
-
mesh['scalars'] = first.ravel()
|
| 927 |
-
actor = movie_plotter.add_mesh(
|
| 928 |
-
mesh,
|
| 929 |
-
scalars='scalars',
|
| 930 |
-
clim=surface_clims[field],
|
| 931 |
-
cmap="RdBu",
|
| 932 |
-
show_scalar_bar=False,
|
| 933 |
-
show_edges=True,
|
| 934 |
-
edge_color='grey',
|
| 935 |
-
line_width=0.5,
|
| 936 |
-
)
|
| 937 |
-
movie_plotter.add_axes()
|
| 938 |
-
# Use similar camera if available, else default
|
| 939 |
-
try:
|
| 940 |
-
if hasattr(plotter, 'camera_position') and plotter.camera_position:
|
| 941 |
-
movie_plotter.camera_position = plotter.camera_position
|
| 942 |
-
else:
|
| 943 |
-
movie_plotter.view_isometric()
|
| 944 |
-
except Exception:
|
| 945 |
-
movie_plotter.view_isometric()
|
| 946 |
-
|
| 947 |
-
movie_plotter.open_movie(path, framerate=20)
|
| 948 |
-
n_frames = len(frames)
|
| 949 |
-
for z_data in frames:
|
| 950 |
-
if mesh.n_points != z_data.size:
|
| 951 |
-
# Rebuild mesh if topology changes (unlikely here)
|
| 952 |
-
points = np.c_[X.ravel(), Y.ravel(), z_data.ravel() * z_scale]
|
| 953 |
-
poly = pv.PolyData(points)
|
| 954 |
-
mesh = poly.delaunay_2d()
|
| 955 |
-
mesh['scalars'] = z_data.ravel()
|
| 956 |
-
movie_plotter.clear()
|
| 957 |
-
actor = movie_plotter.add_mesh(
|
| 958 |
-
mesh,
|
| 959 |
-
scalars='scalars',
|
| 960 |
-
clim=surface_clims[field],
|
| 961 |
-
cmap="RdBu",
|
| 962 |
-
show_scalar_bar=False,
|
| 963 |
-
show_edges=True,
|
| 964 |
-
edge_color='grey',
|
| 965 |
-
line_width=0.5,
|
| 966 |
-
)
|
| 967 |
-
else:
|
| 968 |
-
mesh.points[:, 2] = z_data.ravel() * z_scale
|
| 969 |
-
mesh['scalars'] = z_data.ravel()
|
| 970 |
-
movie_plotter.render()
|
| 971 |
-
movie_plotter.write_frame()
|
| 972 |
-
movie_plotter.close()
|
| 973 |
|
| 974 |
-
state.export_status_message = f"Exported MP4 to {path}"
|
| 975 |
-
except Exception as e:
|
| 976 |
-
state.export_status_message = f"Export failed: {e}"
|
| 977 |
-
finally:
|
| 978 |
-
state.show_export_status = True
|
| 979 |
-
|
| 980 |
-
# --- Small Plot under Meshing: Qubit requirement vs Grid Size ---
|
| 981 |
-
def build_qubit_plot(grid_size: int):
|
| 982 |
-
x_sizes = np.array([16, 32, 64, 128, 256, 512])
|
| 983 |
-
y_qubits = 2 * np.ceil(np.log2(x_sizes)).astype(int) + 3
|
| 984 |
-
current_nq = int(2 * np.ceil(np.log2(max(1, int(grid_size)))) + 3)
|
| 985 |
-
|
| 986 |
-
fig = go.Figure()
|
| 987 |
-
# Match app.py: x = grid size, y = total qubits
|
| 988 |
-
fig.add_trace(go.Scatter(x=x_sizes, y=y_qubits, mode='lines', name='Total Qubits', line=dict(color='#7A3DB5', width=3)))
|
| 989 |
-
fig.add_trace(go.Scatter(x=[grid_size], y=[current_nq], mode='markers', marker=dict(size=10, color='#5F259F'), name='Current Selection'))
|
| 990 |
-
|
| 991 |
-
x_min = int(x_sizes.min()); x_max = int(x_sizes.max())
|
| 992 |
-
y_min = int(y_qubits.min()); y_max = int(max(y_qubits.max(), current_nq))
|
| 993 |
-
fig.update_xaxes(range=[x_min - 8, x_max + 8], tickmode='array', tickvals=x_sizes, ticktext=[str(v) for v in x_sizes], title_text="Grid Size (nx)", gridcolor='rgba(95,37,159,0.1)', zerolinecolor='rgba(95,37,159,0.3)')
|
| 994 |
-
fig.update_yaxes(range=[y_min - 1, y_max + 1], dtick=1, title_text="Total Qubits (nq)", gridcolor='rgba(95,37,159,0.1)', zerolinecolor='rgba(95,37,159,0.3)')
|
| 995 |
-
fig.update_layout(
|
| 996 |
-
margin=dict(l=30, r=10, t=10, b=30),
|
| 997 |
-
autosize=True,
|
| 998 |
-
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| 999 |
-
font=dict(color='#1A1A1A'),
|
| 1000 |
-
paper_bgcolor='#FFFFFF',
|
| 1001 |
-
plot_bgcolor='#FFFFFF',
|
| 1002 |
-
colorway=['#5F259F', '#7A3DB5', '#AE8BD8', '#5F259F'],
|
| 1003 |
-
)
|
| 1004 |
-
return fig
|
| 1005 |
-
|
| 1006 |
-
# --- UI Layout ---
|
| 1007 |
with SinglePageLayout(server) as layout:
|
| 1008 |
-
layout.title.set_text("
|
| 1009 |
-
# Synopsys branding: primary purple + shades, white surface, dark text
|
| 1010 |
-
layout.title.style = "color: #5f259f; font-weight: 600;"
|
| 1011 |
layout.toolbar.classes = "pl-2 pr-1 py-1 elevation-0"
|
| 1012 |
layout.toolbar.style = "background-color: #ffffff; border-bottom: 3px solid #5f259f;"
|
| 1013 |
-
|
| 1014 |
-
"""
|
| 1015 |
-
:root{
|
| 1016 |
-
--v-theme-primary: 95, 37, 159; /* #5F259F */
|
| 1017 |
-
--v-theme-secondary: 122, 61, 181; /* #7A3DB5 */
|
| 1018 |
-
--v-theme-accent: 174, 139, 216; /* #AE8BD8 */
|
| 1019 |
-
--v-theme-surface: 255, 255, 255; /* #FFFFFF */
|
| 1020 |
-
--v-theme-background: 255, 255, 255; /* #FFFFFF */
|
| 1021 |
-
--v-theme-on-primary: 255, 255, 255; /* #FFFFFF */
|
| 1022 |
-
--v-theme-on-surface: 26, 26, 26; /* #1A1A1A */
|
| 1023 |
-
}
|
| 1024 |
-
.syn-title{ color:#5f259f !important; }
|
| 1025 |
-
.syn-border-bottom{ border-bottom:3px solid #5f259f !important; }
|
| 1026 |
-
.syn-bg-white{ background:#ffffff !important; }
|
| 1027 |
-
/* Synopsys UI refinements */
|
| 1028 |
-
a, .syn-link { color: #5f259f; text-decoration: none; }
|
| 1029 |
-
a:hover, .syn-link:hover { color: #7A3DB5; text-decoration: underline; }
|
| 1030 |
-
.v-list .v-list-item:hover { background-color: rgba(95,37,159,.08) !important; }
|
| 1031 |
-
.v-list-item--active { background-color: rgba(95,37,159,.16) !important; color: #5f259f !important; }
|
| 1032 |
-
"""
|
| 1033 |
-
)
|
| 1034 |
with layout.toolbar:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1035 |
vuetify3.VSpacer()
|
| 1036 |
vuetify3.VImg(
|
| 1037 |
v_if="logo_src",
|
| 1038 |
src=("logo_src", None),
|
| 1039 |
-
style="height:
|
| 1040 |
-
classes="
|
| 1041 |
)
|
| 1042 |
-
with layout.content:
|
| 1043 |
-
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
|
| 1044 |
-
with vuetify3.VDialog(v_model=("show_upload_dialog", False), max_width="500px"):
|
| 1045 |
-
with vuetify3.VCard():
|
| 1046 |
-
vuetify3.VCardTitle("Upload Geometry")
|
| 1047 |
-
with vuetify3.VCardText():
|
| 1048 |
-
vuetify3.VFileInput(
|
| 1049 |
-
show_size=True,
|
| 1050 |
-
label="Select geometry file",
|
| 1051 |
-
accept=".vtp,.vtk,.glb,.stl",
|
| 1052 |
-
update_binary=("uploaded_file_info", 1),
|
| 1053 |
-
)
|
| 1054 |
-
with vuetify3.VCardActions():
|
| 1055 |
-
vuetify3.VSpacer()
|
| 1056 |
-
vuetify3.VBtn("Cancel", click="show_upload_dialog = false")
|
| 1057 |
-
|
| 1058 |
-
vuetify3.VSnackbar(
|
| 1059 |
-
v_model=("show_upload_status", False),
|
| 1060 |
-
children=["{{ upload_status_message }}"],
|
| 1061 |
-
timeout=4000,
|
| 1062 |
-
location="bottom right",
|
| 1063 |
-
color="primary",
|
| 1064 |
-
variant="tonal",
|
| 1065 |
-
)
|
| 1066 |
-
vuetify3.VSnackbar(
|
| 1067 |
-
v_model=("show_export_status", False),
|
| 1068 |
-
children=["{{ export_status_message }}"],
|
| 1069 |
-
timeout=4000,
|
| 1070 |
-
location="bottom right",
|
| 1071 |
-
color="primary",
|
| 1072 |
-
variant="tonal",
|
| 1073 |
-
)
|
| 1074 |
-
|
| 1075 |
-
with vuetify3.VRow(no_gutters=True, classes="fill-height"):
|
| 1076 |
-
with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
|
| 1077 |
-
# Cell 1: Introduction
|
| 1078 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1079 |
-
with vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary"):
|
| 1080 |
-
pass
|
| 1081 |
-
with vuetify3.VCardText():
|
| 1082 |
-
# Removed subtitle and restyled sections
|
| 1083 |
-
vuetify3.VDivider(classes="my-2")
|
| 1084 |
-
vuetify3.VCardSubtitle("Problem", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 1085 |
-
vuetify3.VDivider(classes="mb-1")
|
| 1086 |
-
vuetify3.VList(
|
| 1087 |
-
density="compact",
|
| 1088 |
-
lines="one",
|
| 1089 |
-
items=(
|
| 1090 |
-
"intro_items_problem",
|
| 1091 |
-
[
|
| 1092 |
-
{"title": "1. Propagation in a given medium (no bodies)"},
|
| 1093 |
-
{"title": "2. Scattering from a perfectly conducting body"},
|
| 1094 |
-
],
|
| 1095 |
-
),
|
| 1096 |
-
)
|
| 1097 |
-
vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 1098 |
-
vuetify3.VDivider(classes="mb-1")
|
| 1099 |
-
vuetify3.VListItemTitle("Maxwell’s time-domain, 2D, TEz polarized.", classes="text-body-2")
|
| 1100 |
-
vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 1101 |
-
vuetify3.VDivider(classes="mb-1")
|
| 1102 |
-
vuetify3.VListItemTitle("Geometry, excitation, medium, output visualization preferences.", classes="text-body-2")
|
| 1103 |
-
vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 1104 |
-
vuetify3.VDivider(classes="mb-1")
|
| 1105 |
-
vuetify3.VListItemTitle("Surface plots of field components OR time evolution of field components at specified points.", classes="text-body-2")
|
| 1106 |
-
# Cell 2: Geometry
|
| 1107 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1108 |
-
with vuetify3.VCardTitle("Geometry", classes="text-primary"):
|
| 1109 |
-
pass
|
| 1110 |
-
with vuetify3.VCardText():
|
| 1111 |
-
vuetify3.VSelect(
|
| 1112 |
-
label="Select",
|
| 1113 |
-
v_model=("geometry_selection", None),
|
| 1114 |
-
items=("geometry_options", ["None", "Square Metallic Body", "Square Domain", "Geometry 2", "Add"]),
|
| 1115 |
-
placeholder="Select",
|
| 1116 |
-
density="compact",
|
| 1117 |
-
color="primary",
|
| 1118 |
-
)
|
| 1119 |
-
with vuetify3.VContainer(v_if="geometry_selection === 'Square Metallic Body'", classes="pa-0 mt-2"):
|
| 1120 |
-
with vuetify3.VRow(dense=True):
|
| 1121 |
-
with vuetify3.VCol():
|
| 1122 |
-
with vuetify3.VTooltip("Square hole edge length s in domain units [0,1]. Must be ≤ 1. UI-only.", location="bottom", color="primary"):
|
| 1123 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1124 |
-
vuetify3.VTextField(
|
| 1125 |
-
v_bind="props",
|
| 1126 |
-
v_model=("hole_size_edge", 0.2),
|
| 1127 |
-
label="Hole Edge Length [0 - 1]",
|
| 1128 |
-
type="number",
|
| 1129 |
-
step=0.05,
|
| 1130 |
-
min=0,
|
| 1131 |
-
density="compact",
|
| 1132 |
-
color="primary",
|
| 1133 |
-
)
|
| 1134 |
-
with vuetify3.VCol():
|
| 1135 |
-
with vuetify3.VTooltip("Hole center as (x, y). Both x and y must be strictly within (0,1). Example: (0.5, 0.5). UI-only.", location="bottom", color="primary"):
|
| 1136 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1137 |
-
vuetify3.VTextField(
|
| 1138 |
-
v_bind="props",
|
| 1139 |
-
v_model=("hole_center_pair", "(0.5, 0.5)"),
|
| 1140 |
-
label="Hole Center (X, Y)",
|
| 1141 |
-
density="compact",
|
| 1142 |
-
color="primary",
|
| 1143 |
-
)
|
| 1144 |
-
with vuetify3.VRow(dense=True, classes="mt-1"):
|
| 1145 |
-
with vuetify3.VCol(cols=12):
|
| 1146 |
-
vuetify3.VSwitch(
|
| 1147 |
-
v_model=("hole_snap", True),
|
| 1148 |
-
label="Snap edges to nearest grid lines",
|
| 1149 |
-
color="primary",
|
| 1150 |
-
inset=True,
|
| 1151 |
-
density="compact",
|
| 1152 |
-
)
|
| 1153 |
-
vuetify3.VAlert(
|
| 1154 |
-
v_if="hole_error_message",
|
| 1155 |
-
type="error",
|
| 1156 |
-
variant="tonal",
|
| 1157 |
-
density="compact",
|
| 1158 |
-
children=["{{ hole_error_message }}"],
|
| 1159 |
-
classes="mt-2",
|
| 1160 |
-
)
|
| 1161 |
-
# Restoring all subsequent input cards
|
| 1162 |
-
# Cell 4: Excitation
|
| 1163 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1164 |
-
with vuetify3.VCardTitle("Excitation: Initial State", classes="text-primary"):
|
| 1165 |
-
pass
|
| 1166 |
-
with vuetify3.VCardText():
|
| 1167 |
-
vuetify3.VSelect(
|
| 1168 |
-
label="Select",
|
| 1169 |
-
v_model=("dist_type", None),
|
| 1170 |
-
items=("dist_type_options", ["None", "Delta", "Gaussian"]),
|
| 1171 |
-
placeholder="Select",
|
| 1172 |
-
density="compact",
|
| 1173 |
-
color="primary",
|
| 1174 |
-
)
|
| 1175 |
-
with vuetify3.VContainer(v_if="dist_type === 'Delta'", classes="pa-0"):
|
| 1176 |
-
with vuetify3.VTooltip("Impulse position (x, y) in [0,1]. Example: (0.6, 0.6).", location="bottom", color="primary"):
|
| 1177 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1178 |
-
vuetify3.VTextField(v_bind="props", v_model=("peak_pair", "(0.5, 0.5)"), label="Peak (x, y) in [0,1]", density="compact", color="primary")
|
| 1179 |
-
with vuetify3.VContainer(v_if="dist_type === 'Gaussian'", classes="pa-0"):
|
| 1180 |
-
with vuetify3.VRow(dense=True):
|
| 1181 |
-
with vuetify3.VCol():
|
| 1182 |
-
with vuetify3.VTooltip("Gaussian center μ (x, y) in [0,1]. Example: (0.5, 0.5).", location="bottom", color="primary"):
|
| 1183 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1184 |
-
vuetify3.VTextField(v_bind="props", v_model=("mu_pair", "(0.5, 0.5)"), label="Mu (x, y) in [0,1]", density="compact", color="primary")
|
| 1185 |
-
# Separate Sigma inputs (normalized)
|
| 1186 |
-
with vuetify3.VRow(dense=True, classes="mt-1"):
|
| 1187 |
-
with vuetify3.VCol():
|
| 1188 |
-
with vuetify3.VTooltip("Gaussian spread σx in [0,1] of domain length.", location="bottom", color="primary"):
|
| 1189 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1190 |
-
vuetify3.VTextField(v_bind="props", v_model=("sigma_x", 0.25), label="Sigma X (0–1)", type="number", step="0.01", density="compact", color="primary")
|
| 1191 |
-
with vuetify3.VCol():
|
| 1192 |
-
with vuetify3.VTooltip("Gaussian spread σy in [0,1] of domain length.", location="bottom", color="primary"):
|
| 1193 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1194 |
-
vuetify3.VTextField(v_bind="props", v_model=("sigma_y", 0.15), label="Sigma Y (0–1)", type="number", step="0.01", density="compact", color="primary")
|
| 1195 |
-
vuetify3.VAlert(v_if="excitation_error_message", type="error", variant="tonal", density="compact", children=["{{ excitation_error_message }}"], classes="mt-2")
|
| 1196 |
-
vuetify3.VAlert(
|
| 1197 |
-
v_if="excitation_info_message",
|
| 1198 |
-
type="info",
|
| 1199 |
-
variant="tonal",
|
| 1200 |
-
density="compact",
|
| 1201 |
-
children=["{{ excitation_info_message }}"],
|
| 1202 |
-
classes="mt-2",
|
| 1203 |
-
)
|
| 1204 |
-
|
| 1205 |
-
# Cell 5: Medium
|
| 1206 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1207 |
-
with vuetify3.VCardTitle("Material Properties (Medium)", classes="text-primary"):
|
| 1208 |
-
pass
|
| 1209 |
-
with vuetify3.VCardText():
|
| 1210 |
-
with vuetify3.VRow(dense=True):
|
| 1211 |
-
with vuetify3.VCol():
|
| 1212 |
-
with vuetify3.VTooltip("Relative permittivity. ε_r = ε / ε₀. Default 1.0 (free space).", location="bottom", color="primary"):
|
| 1213 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1214 |
-
vuetify3.VTextField(v_bind="props", v_model=("coeff_permittivity", 1.0), label="Relative permittivity (ε_r)", type="number", density="compact", color="primary")
|
| 1215 |
-
with vuetify3.VCol():
|
| 1216 |
-
with vuetify3.VTooltip("Relative permeability. μ_r = μ / μ₀. Typically 1.0 for non-magnetic materials.", location="bottom", color="primary"):
|
| 1217 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1218 |
-
vuetify3.VTextField(v_bind="props", v_model=("coeff_permeability", 1.0), label="Relative permeability (μ_r)", type="number", density="compact", color="primary")
|
| 1219 |
-
|
| 1220 |
-
# New Cell: Temporal Settings (moved here)
|
| 1221 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1222 |
-
with vuetify3.VCardTitle("Temporal Settings", classes="text-primary"):
|
| 1223 |
-
pass
|
| 1224 |
-
with vuetify3.VCardText():
|
| 1225 |
-
with vuetify3.VTooltip("Sets the total duration for the simulation to run.", location="bottom", color="primary"):
|
| 1226 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1227 |
-
vuetify3.VTextField(v_bind="props", v_model=("T", 1.0), label="Total Time (T)", type="number", step="0.1", density="compact", color="primary")
|
| 1228 |
-
# Small bold heading before Δt input
|
| 1229 |
-
vuetify3.VCardSubtitle("Select Δt intervals for plotting output snapshots", classes="text-subtitle-2 font-weight-bold mt-2")
|
| 1230 |
-
with vuetify3.VTooltip("Snapshot interval (Δt). Solver runs at fixed 0.1 s; frames are saved every Δt. Values < 0.1 or not multiples of 0.1 are unsupported.", location="bottom", color="primary"):
|
| 1231 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1232 |
-
vuetify3.VTextField(v_bind="props", v_model=("dt_user", 0.1), label="Δt", type="number", step="0.1", density="compact", color="primary", classes="mt-2")
|
| 1233 |
-
vuetify3.VAlert(v_if="temporal_warning", type="warning", variant="tonal", density="compact", children=["{{ temporal_warning }}"], classes="mt-2")
|
| 1234 |
-
# Moved Meshing card: now under Temporal Settings and before Backends
|
| 1235 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1236 |
-
with vuetify3.VCardTitle("Meshing", classes="text-primary"):
|
| 1237 |
-
pass
|
| 1238 |
-
with vuetify3.VCardText():
|
| 1239 |
-
# Show the qubit graph only while hovering over the slider (like app.py)
|
| 1240 |
-
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=False, location="end", offset=8):
|
| 1241 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1242 |
-
with vuetify3.VSlider(
|
| 1243 |
-
v_bind="props",
|
| 1244 |
-
v_model=("nx_slider_index", None),
|
| 1245 |
-
label="No. of points per direction:",
|
| 1246 |
-
min=0,
|
| 1247 |
-
max=5,
|
| 1248 |
-
step=1,
|
| 1249 |
-
show_ticks="always",
|
| 1250 |
-
thumb_label="always",
|
| 1251 |
-
density="compact",
|
| 1252 |
-
color="primary",
|
| 1253 |
-
):
|
| 1254 |
-
vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512][modelValue] }}"])
|
| 1255 |
-
# Hover content: enlarged Plotly graph with app.py axes (x=nx, y=nq)
|
| 1256 |
-
with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 644px;"):
|
| 1257 |
-
qubit_fig_widget = plotly_widgets.Figure(
|
| 1258 |
-
figure=build_qubit_plot(int(state.nx or 16)),
|
| 1259 |
-
responsive=True,
|
| 1260 |
-
style="width: 616px; height: 364px; min-height: 364px;",
|
| 1261 |
-
)
|
| 1262 |
-
# Cell: Backends (from app.py)
|
| 1263 |
-
with vuetify3.VCard(classes="mb-4"):
|
| 1264 |
-
with vuetify3.VCardTitle("Backends", classes="text-primary"):
|
| 1265 |
-
pass
|
| 1266 |
-
with vuetify3.VCardText():
|
| 1267 |
-
with vuetify3.VRow(dense=True, classes="mb-2"):
|
| 1268 |
-
with vuetify3.VCol():
|
| 1269 |
-
vuetify3.VAlert(
|
| 1270 |
-
type="info",
|
| 1271 |
-
color="primary",
|
| 1272 |
-
variant="tonal",
|
| 1273 |
-
density="compact",
|
| 1274 |
-
children=[
|
| 1275 |
-
"Selected: ",
|
| 1276 |
-
"{{ backend_type || '—' }}",
|
| 1277 |
-
" - ",
|
| 1278 |
-
"{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}",
|
| 1279 |
-
],
|
| 1280 |
-
)
|
| 1281 |
-
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
|
| 1282 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1283 |
-
vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
|
| 1284 |
-
with vuetify3.VList(density="compact"):
|
| 1285 |
-
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
|
| 1286 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1287 |
-
vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
|
| 1288 |
-
with vuetify3.VList(density="compact"):
|
| 1289 |
-
vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
|
| 1290 |
-
vuetify3.VListItem(title="IonQ simulator", click="backend_type = 'Simulator'; selected_simulator = 'IonQ simulator'")
|
| 1291 |
-
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
|
| 1292 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1293 |
-
vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
|
| 1294 |
-
with vuetify3.VList(density="compact"):
|
| 1295 |
-
vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
|
| 1296 |
-
vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
|
| 1297 |
-
|
| 1298 |
-
# Run Simulation and Stop Buttons Row
|
| 1299 |
-
with vuetify3.VRow(dense=True, classes="mb-2"):
|
| 1300 |
-
with vuetify3.VCol(cols=9):
|
| 1301 |
-
with vuetify3.VTooltip("Starts the quantum simulation with the specified parameters. This may take some time.", location="bottom", color="primary"):
|
| 1302 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1303 |
-
vuetify3.VBtn(
|
| 1304 |
-
v_bind="props",
|
| 1305 |
-
text=("run_button_text", "Run Simulation"),
|
| 1306 |
-
click=run_simulation_only,
|
| 1307 |
-
color="primary",
|
| 1308 |
-
block=True,
|
| 1309 |
-
disabled=("is_running || run_button_text === 'Successful!' || !geometry_selection || !dist_type || !!temporal_warning || nx === null", False),
|
| 1310 |
-
)
|
| 1311 |
-
with vuetify3.VCol(cols=3):
|
| 1312 |
-
with vuetify3.VTooltip("Stop the running simulation", location="bottom", color="primary"):
|
| 1313 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1314 |
-
vuetify3.VBtn(
|
| 1315 |
-
v_bind="props",
|
| 1316 |
-
text="Stop",
|
| 1317 |
-
click=stop_simulation_handler,
|
| 1318 |
-
color="error",
|
| 1319 |
-
block=True,
|
| 1320 |
-
disabled=("stop_button_disabled", True),
|
| 1321 |
-
)
|
| 1322 |
-
|
| 1323 |
-
# Reset Button
|
| 1324 |
-
with vuetify3.VTooltip("Reset all parameters to their default values", location="bottom", color="primary"):
|
| 1325 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1326 |
-
vuetify3.VBtn(
|
| 1327 |
-
v_bind="props",
|
| 1328 |
-
text="Reset",
|
| 1329 |
-
click=reset_to_defaults,
|
| 1330 |
-
color="secondary",
|
| 1331 |
-
block=True,
|
| 1332 |
-
classes="mt-auto"
|
| 1333 |
-
)
|
| 1334 |
-
|
| 1335 |
-
# Main graph column
|
| 1336 |
-
with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
|
| 1337 |
-
# Output Configuration (appears after simulation)
|
| 1338 |
-
with vuetify3.VCard(v_if="simulation_has_run", classes="mb-4"):
|
| 1339 |
-
with vuetify3.VCardSubtitle("Output Configuration", classes="text-primary"):
|
| 1340 |
-
with vuetify3.VCardText():
|
| 1341 |
-
with vuetify3.VRadioGroup(v_model=("output_type", "Surface Plot"), row=True, density="compact", color="primary"):
|
| 1342 |
-
vuetify3.VRadio(label="Surface", value="Surface Plot")
|
| 1343 |
-
vuetify3.VRadio(label="Time Series", value="Time Series Plot")
|
| 1344 |
-
with vuetify3.VContainer(v_if="output_type === 'Surface Plot'", classes="pa-0"):
|
| 1345 |
-
vuetify3.VSelect(v_model=("surface_field", "Ez"), items=("surface_field_options", ["Ez", "Hx", "Hy"]), label="Field Component", density="compact", color="primary")
|
| 1346 |
-
# Replace export format dropdown and individual buttons with a single Export menu
|
| 1347 |
-
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
|
| 1348 |
-
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 1349 |
-
vuetify3.VBtn(v_bind="props", text="Export", color="primary", variant="tonal", block=True, classes="mt-2")
|
| 1350 |
-
with vuetify3.VList(density="compact"):
|
| 1351 |
-
vuetify3.VListSubheader("VTK")
|
| 1352 |
-
vuetify3.VListItem(title="Current frame (VTK)", prepend_icon="mdi-download", click=export_vtk)
|
| 1353 |
-
vuetify3.VListItem(title="All frames (VTK sequence)", prepend_icon="mdi-download-multiple", click=export_vtk_all_frames)
|
| 1354 |
-
vuetify3.VDivider()
|
| 1355 |
-
vuetify3.VListItem(title="Animation (MP4)", prepend_icon="mdi-movie", click=export_mp4)
|
| 1356 |
-
with vuetify3.VContainer(v_if="output_type === 'Time Series Plot'", classes="pa-0"):
|
| 1357 |
-
vuetify3.VSelect(v_model=("timeseries_field", "Ez"), items=("timeseries_field_options", ["Ez", "Hx", "Hy"]), label="Field Component", density="compact", color="primary")
|
| 1358 |
-
vuetify3.VTextarea(v_model=("timeseries_points", "(8, 8), (10, 8)"), label="Monitor Points", hint="e.g., (8, 8), (10, 8)", rows=2, auto_grow=True, color="primary")
|
| 1359 |
-
|
| 1360 |
-
# Main plot area (hidden until geometry selected)
|
| 1361 |
-
with vuetify3.VCard(v_if="geometry_selection", classes="flex-grow-1", style="min-height: 0;"):
|
| 1362 |
-
with vuetify3.VContainer(v_if="is_running", fluid=True, classes="fill-height d-flex flex-column align-center justify-center"):
|
| 1363 |
-
vuetify3.VProgressCircular(indeterminate=True, size=64, color="primary")
|
| 1364 |
-
vuetify3.VCardSubtitle("Running simulation...", classes="mt-4")
|
| 1365 |
-
with vuetify3.VContainer(v_if="!is_running", fluid=True, classes="fill-height pa-0", style=("pyvista_view_style", "aspect-ratio: 1 / 1; width: 100%;")):
|
| 1366 |
-
view = plotter_ui(plotter)
|
| 1367 |
-
ctrl.view_update = view.update
|
| 1368 |
-
|
| 1369 |
-
# Placeholder when no geometry selected
|
| 1370 |
-
with vuetify3.VContainer(v_if="!geometry_selection", fluid=True, classes="flex-grow-1 d-flex align-center justify-center text-medium-emphasis"):
|
| 1371 |
-
vuetify3.VCardText("Select a geometry to display the preview and results.")
|
| 1372 |
-
|
| 1373 |
-
# Time slider for surface plot (restored to right panel)
|
| 1374 |
-
with vuetify3.VContainer(v_if="simulation_has_run && output_type === 'Surface Plot'", fluid=True, classes="pa-0 mt-4"):
|
| 1375 |
-
vuetify3.VSlider(v_model=("time_val", 0.0), label="Time", min=0, max=("T", 10.0), step=("dt_user", 0.1), thumb_label="always", density="compact", color="primary")
|
| 1376 |
-
|
| 1377 |
-
# Store the widget's update method in the controller for later updates
|
| 1378 |
-
ctrl.qubit_plot_update = qubit_fig_widget.update
|
| 1379 |
-
|
| 1380 |
-
@state.change("nx")
|
| 1381 |
-
def update_qubit_plot(nx, **kwargs):
|
| 1382 |
-
try:
|
| 1383 |
-
ctrl.qubit_plot_update(build_qubit_plot(int(nx)))
|
| 1384 |
-
except Exception:
|
| 1385 |
-
pass
|
| 1386 |
-
|
| 1387 |
-
@state.change("hole_size_edge", "hole_center_x", "hole_center_y", "geometry_selection", "hole_snap")
|
| 1388 |
-
def validate_hole_inputs(**kwargs):
|
| 1389 |
-
# Only validate when Square Metallic Body is selected
|
| 1390 |
-
if state.geometry_selection != "Square Metallic Body":
|
| 1391 |
-
state.hole_error_message = ""
|
| 1392 |
-
return
|
| 1393 |
-
try:
|
| 1394 |
-
s = float(state.hole_size_edge)
|
| 1395 |
-
cx = float(state.hole_center_x)
|
| 1396 |
-
cy = float(state.hole_center_y)
|
| 1397 |
-
except Exception:
|
| 1398 |
-
state.hole_error_message = "Hole size and center must be numeric."
|
| 1399 |
-
return
|
| 1400 |
-
|
| 1401 |
-
# Use selected nx, fall back to a safe default when not selected yet
|
| 1402 |
-
try:
|
| 1403 |
-
nx = int(state.nx or 32)
|
| 1404 |
-
except Exception:
|
| 1405 |
-
nx = 32
|
| 1406 |
-
|
| 1407 |
-
if s > 1.0:
|
| 1408 |
-
state.hole_error_message = "Hole edge length must be <= 1."
|
| 1409 |
-
return
|
| 1410 |
-
if not (0.0 < cx < 1.0) or not (0.0 < cy < 1.0):
|
| 1411 |
-
state.hole_error_message = "Hole center must be strictly within (0, 1) for both X and Y."
|
| 1412 |
-
return
|
| 1413 |
-
|
| 1414 |
-
# Alignment check (strict vs snap)
|
| 1415 |
-
mode_snap = bool(state.hole_snap)
|
| 1416 |
-
edges = _compute_hole_edges(nx, cx, cy, s, snap=mode_snap)
|
| 1417 |
-
if edges is None and not mode_snap:
|
| 1418 |
-
h = _grid_spacing(nx)
|
| 1419 |
-
state.hole_error_message = f"Strict alignment failed: edges must lie on grid lines k·h, h=1/(nx-1) ≈ {h:.4f}."
|
| 1420 |
-
return
|
| 1421 |
-
|
| 1422 |
-
state.hole_error_message = ""
|
| 1423 |
-
|
| 1424 |
-
# Refresh preview if applicable
|
| 1425 |
-
if state.geometry_selection == "Square Metallic Body" and not state.is_running and not state.simulation_has_run:
|
| 1426 |
-
update_geometry_hole_preview()
|
| 1427 |
-
|
| 1428 |
-
def _grid_spacing(nx: int) -> float:
|
| 1429 |
-
return 1.0 / float(max(int(nx) - 1, 1))
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
def _is_on_grid(val: float, h: float, atol: float = 1e-9) -> bool:
|
| 1433 |
-
r = val / h
|
| 1434 |
-
return abs(r - round(r)) <= atol
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
def _snap_to_grid(val: float, h: float) -> float:
|
| 1438 |
-
return round(val / h) * h
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
def _compute_hole_edges(nx: int, cx: float, cy: float, a: float, snap: bool, atol: float = 1e-9):
|
| 1442 |
-
"""
|
| 1443 |
-
Build requested open rectangle edges from center (cx, cy) and size a, then either
|
| 1444 |
-
validate (strict) or quantize (snap) to grid P = {k*h} with h = 1/(nx-1).
|
| 1445 |
-
Returns (xL, xR, yB, yT) or None if strict alignment fails.
|
| 1446 |
-
"""
|
| 1447 |
-
h = _grid_spacing(nx)
|
| 1448 |
-
# Requested edges
|
| 1449 |
-
xL_req, xR_req = cx - a / 2.0, cx + a / 2.0
|
| 1450 |
-
yB_req, yT_req = cy - a / 2.0, cy + a / 2.0
|
| 1451 |
-
|
| 1452 |
-
# Keep edges within (0,1) for numerical stability; open set semantics unchanged
|
| 1453 |
-
eps = 1e-12
|
| 1454 |
-
xL_req = max(eps, min(1.0 - eps, xL_req))
|
| 1455 |
-
xR_req = max(eps, min(1.0 - eps, xR_req))
|
| 1456 |
-
yB_req = max(eps, min(1.0 - eps, yB_req))
|
| 1457 |
-
yT_req = max(eps, min(1.0 - eps, yT_req))
|
| 1458 |
-
|
| 1459 |
-
if not snap:
|
| 1460 |
-
ok = all(_is_on_grid(v, h, atol) for v in (xL_req, xR_req, yB_req, yT_req))
|
| 1461 |
-
if not ok:
|
| 1462 |
-
return None
|
| 1463 |
-
return xL_req, xR_req, yB_req, yT_req
|
| 1464 |
-
|
| 1465 |
-
# Snap each edge independently
|
| 1466 |
-
xL, xR = _snap_to_grid(xL_req, h), _snap_to_grid(xR_req, h)
|
| 1467 |
-
yB, yT = _snap_to_grid(yB_req, h), _snap_to_grid(yT_req, h)
|
| 1468 |
-
|
| 1469 |
-
# Ensure proper ordering and minimum width/height (at least one cell)
|
| 1470 |
-
if xL >= xR:
|
| 1471 |
-
cx_idx = round(cx / h)
|
| 1472 |
-
xL = max(h, (cx_idx - 1) * h)
|
| 1473 |
-
xR = min(1.0 - h, (cx_idx + 1) * h)
|
| 1474 |
-
if yB >= yT:
|
| 1475 |
-
cy_idx = round(cy / h)
|
| 1476 |
-
yB = max(h, (cy_idx - 1) * h)
|
| 1477 |
-
yT = min(1.0 - h, (cy_idx + 1) * h)
|
| 1478 |
-
|
| 1479 |
-
# Clamp into (0,1)
|
| 1480 |
-
xL = max(eps, min(1.0 - eps, xL))
|
| 1481 |
-
xR = max(eps, min(1.0 - eps, xR))
|
| 1482 |
-
yB = max(eps, min(1.0 - eps, yB))
|
| 1483 |
-
yT = max(eps, min(1.0 - eps, yT))
|
| 1484 |
-
|
| 1485 |
-
if xL < xR and yB < yT:
|
| 1486 |
-
return xL, xR, yB, yT
|
| 1487 |
-
return None
|
| 1488 |
-
|
| 1489 |
-
@state.change("hole_center_pair")
|
| 1490 |
-
def sync_hole_center_pair(hole_center_pair, **kwargs):
|
| 1491 |
-
"""Parse bracket-format pair (x, y) from dropdown into numeric center fields."""
|
| 1492 |
-
try:
|
| 1493 |
-
m = re.match(r"\(\s*([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*\)", str(hole_center_pair))
|
| 1494 |
-
if not m:
|
| 1495 |
-
raise ValueError("Invalid format")
|
| 1496 |
-
state.hole_center_x = float(m.group(1))
|
| 1497 |
-
state.hole_center_y = float(m.group(2))
|
| 1498 |
-
# Defer range validation to validate_hole_inputs
|
| 1499 |
-
except Exception:
|
| 1500 |
-
state.hole_error_message = "Invalid hole center. Use format (x, y)."
|
| 1501 |
-
|
| 1502 |
-
@state.change("sigma_pair")
|
| 1503 |
-
def sync_sigma_pair(sigma_pair, **kwargs):
|
| 1504 |
-
"""Parse bracket-format pair (x, y) for Sigma and update sigma_x/sigma_y."""
|
| 1505 |
-
try:
|
| 1506 |
-
m = re.match(r"\(\s*([-+]?[0-9]*\.?[0-9]+)\s*,\s*([-+]?[0-9]*\.?[0-9]+)\s*\)", str(sigma_pair))
|
| 1507 |
-
if not m:
|
| 1508 |
-
raise ValueError("Invalid format")
|
| 1509 |
-
x = max(0.0, min(1.0, float(m.group(1))))
|
| 1510 |
-
y = max(0.0, min(1.0, float(m.group(2))))
|
| 1511 |
-
state.sigma_x = x
|
| 1512 |
-
state.sigma_y = y
|
| 1513 |
-
state.excitation_error_message = ""
|
| 1514 |
-
except Exception:
|
| 1515 |
-
state.excitation_error_message = "Invalid Sigma. Use format (x, y) in [0,1]."
|
| 1516 |
-
|
| 1517 |
-
@state.change("dist_type")
|
| 1518 |
-
def normalize_dist_type(dist_type, **kwargs):
|
| 1519 |
-
# Allow unselecting via 'None'
|
| 1520 |
-
if dist_type in (None, "", "None"):
|
| 1521 |
-
state.dist_type = None
|
| 1522 |
-
update_initial_state_preview()
|
| 1523 |
-
update_excitation_info_message()
|
| 1524 |
-
|
| 1525 |
-
@state.change("dt_user")
|
| 1526 |
-
def validate_dt_user(dt_user, **kwargs):
|
| 1527 |
-
"""Validate snapshot Δt: must be >= 0.1 (solver dt) and a multiple of 0.1."""
|
| 1528 |
-
try:
|
| 1529 |
-
dt_val = float(dt_user)
|
| 1530 |
-
except Exception:
|
| 1531 |
-
state.temporal_warning = "Δt must be numeric. Frames are captured every Δt."
|
| 1532 |
-
return
|
| 1533 |
-
tol = 1e-9
|
| 1534 |
-
if dt_val < 0.1 - tol:
|
| 1535 |
-
state.temporal_warning = "Δt < 0.1 is unsupported (solver dt = 0.1 s)."
|
| 1536 |
-
elif abs((dt_val / 0.1) - round(dt_val / 0.1)) > 1e-9:
|
| 1537 |
-
state.temporal_warning = "Δt must be a multiple of 0.1 s."
|
| 1538 |
-
else:
|
| 1539 |
-
state.temporal_warning = ""
|
| 1540 |
-
|
| 1541 |
-
# --- Initial Setup Call ---
|
| 1542 |
-
update_initial_state_preview()
|
| 1543 |
-
|
| 1544 |
-
server.start()
|
| 1545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1546 |
|
| 1547 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from trame.app import get_server
|
| 2 |
from trame_vuetify.ui.vuetify3 import SinglePageLayout
|
| 3 |
from trame_vuetify.widgets import vuetify3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
from trame.widgets import html as trame_html
|
| 5 |
+
import os
|
| 6 |
|
| 7 |
+
# Import pages
|
| 8 |
+
from pages import em_page
|
| 9 |
+
import qlbm as qlbm_page
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
# Create a single server for the multipage app
|
| 12 |
server = get_server()
|
| 13 |
state, ctrl = server.state, server.controller
|
| 14 |
|
| 15 |
+
# App state: track which page is active
|
| 16 |
+
state.current_page = "EM" # or "QLBM"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
# Load Synopsys/Ansys logo as data URI for main toolbar
|
| 19 |
+
import base64
|
| 20 |
|
| 21 |
+
def _load_logo_data_uri():
|
|
|
|
| 22 |
base_dir = os.path.dirname(__file__)
|
| 23 |
candidates = [
|
| 24 |
+
os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg"),
|
| 25 |
os.path.join(base_dir, "synopsys-logo-color-rgb.svg"),
|
| 26 |
os.path.join(base_dir, "synopsys-logo-color-rgb.png"),
|
| 27 |
os.path.join(base_dir, "synopsys-logo-color-rgb.jpg"),
|
|
|
|
| 35 |
return f"data:{mime};base64,{b64}"
|
| 36 |
return None
|
| 37 |
|
| 38 |
+
# Safely initialize logo in state (trame state isn't a dict; avoid .get())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
try:
|
| 40 |
+
if not hasattr(state, "logo_src") or state.logo_src in (None, ""):
|
| 41 |
+
state.logo_src = _load_logo_data_uri()
|
| 42 |
except Exception:
|
| 43 |
+
state.logo_src = _load_logo_data_uri()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
with SinglePageLayout(server) as layout:
|
| 46 |
+
layout.title.set_text("Quantum Applications")
|
|
|
|
|
|
|
| 47 |
layout.toolbar.classes = "pl-2 pr-1 py-1 elevation-0"
|
| 48 |
layout.toolbar.style = "background-color: #ffffff; border-bottom: 3px solid #5f259f;"
|
| 49 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
with layout.toolbar:
|
| 51 |
+
# Simple navigation with tabs
|
| 52 |
+
with vuetify3.VTabs(v_model=("current_page", "EM"), grow=True, density="compact", color="primary"):
|
| 53 |
+
vuetify3.VTab(value="EM", children=["EM Scattering"])
|
| 54 |
+
vuetify3.VTab(value="QLBM", children=["Fluids"]) # renamed label only
|
| 55 |
+
# Right-aligned logo
|
| 56 |
vuetify3.VSpacer()
|
| 57 |
vuetify3.VImg(
|
| 58 |
v_if="logo_src",
|
| 59 |
src=("logo_src", None),
|
| 60 |
+
style="height: 40px; width: auto;", # slightly smaller than app bar height
|
| 61 |
+
classes="ml-2",
|
| 62 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
with layout.content:
|
| 65 |
+
# Mount EM page
|
| 66 |
+
with vuetify3.VContainer(v_if="current_page === 'EM'", fluid=True, classes="pa-0 fill-height"):
|
| 67 |
+
em_page.build(server)
|
| 68 |
+
# Mount QLBM page
|
| 69 |
+
with vuetify3.VContainer(v_if="current_page === 'QLBM'", fluid=True, classes="pa-0 fill-height"):
|
| 70 |
+
qlbm_page.build(server)
|
| 71 |
+
|
| 72 |
+
port = int(os.environ.get("PORT", "8080"))
|
| 73 |
+
server.start(address="0.0.0.0", port=port, open_browser=True)
|
| 74 |
|
| 75 |
|
em_trame.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
pages/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Make pages a package and re-export builders for convenience
|
| 2 |
+
from . import em_page, qlbm_page
|
| 3 |
+
|
| 4 |
+
__all__ = ["em_page", "qlbm_page"]
|
pages/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (311 Bytes). View file
|
|
|
pages/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (284 Bytes). View file
|
|
|
pages/__pycache__/em_page.cpython-311.pyc
ADDED
|
Binary file (3.43 kB). View file
|
|
|
pages/__pycache__/em_page.cpython-313.pyc
ADDED
|
Binary file (1.56 kB). View file
|
|
|
pages/__pycache__/qlbm_page.cpython-311.pyc
ADDED
|
Binary file (18.3 kB). View file
|
|
|
pages/__pycache__/qlbm_page.cpython-313.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
pages/em_page.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from trame_vuetify.widgets import vuetify3
|
| 2 |
+
from trame.widgets import html as trame_html
|
| 3 |
+
import os
|
| 4 |
+
import subprocess
|
| 5 |
+
import sys
|
| 6 |
+
import atexit
|
| 7 |
+
|
| 8 |
+
# Keep a single child process for the EM app
|
| 9 |
+
_em_proc = None
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _kill_em_process():
|
| 13 |
+
"""Ensure any running EM process is terminated."""
|
| 14 |
+
global _em_proc
|
| 15 |
+
if _em_proc and _em_proc.poll() is None:
|
| 16 |
+
try:
|
| 17 |
+
_em_proc.terminate()
|
| 18 |
+
_em_proc.wait(timeout=2)
|
| 19 |
+
except Exception:
|
| 20 |
+
try:
|
| 21 |
+
_em_proc.kill()
|
| 22 |
+
except Exception:
|
| 23 |
+
pass
|
| 24 |
+
_em_proc = None
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _ensure_em_process_started():
|
| 28 |
+
global _em_proc
|
| 29 |
+
# Check if process is still running
|
| 30 |
+
if (_em_proc and _em_proc.poll() is None):
|
| 31 |
+
return
|
| 32 |
+
|
| 33 |
+
# Kill any stale process first
|
| 34 |
+
_kill_em_process()
|
| 35 |
+
|
| 36 |
+
base_dir = os.path.dirname(os.path.dirname(__file__))
|
| 37 |
+
em_path = os.path.join(base_dir, "em_trame.py")
|
| 38 |
+
env = os.environ.copy()
|
| 39 |
+
# Port used by iframe
|
| 40 |
+
env.setdefault("EM_APP_PORT", env.get("PORT_EM", "8701"))
|
| 41 |
+
# Start em_trame.py in a separate process
|
| 42 |
+
python_exe = sys.executable or "python"
|
| 43 |
+
_em_proc = subprocess.Popen([python_exe, em_path], cwd=base_dir, env=env)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# Register cleanup on exit
|
| 47 |
+
atexit.register(_kill_em_process)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def build(server):
|
| 51 |
+
"""Render the EM app via iframe and ensure its process is running."""
|
| 52 |
+
_ensure_em_process_started()
|
| 53 |
+
port = os.environ.get("EM_APP_PORT", os.environ.get("PORT_EM", "8701"))
|
| 54 |
+
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
|
| 55 |
+
trame_html.Iframe(
|
| 56 |
+
src=("em_iframe_src", f"http://localhost:{port}/"),
|
| 57 |
+
style="border:0; width:100%; height: calc(100vh - 64px);",
|
| 58 |
+
)
|
| 59 |
+
trame_html.Div(
|
| 60 |
+
"If the EM page is blank, wait a few seconds for the subprocess to start.",
|
| 61 |
+
style="color: rgba(0,0,0,.6); padding: 6px;",
|
| 62 |
+
)
|
pages/qlbm_page.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from trame_vuetify.widgets import vuetify3
|
| 2 |
+
from trame.widgets import html as trame_html
|
| 3 |
+
from trame_plotly.widgets import plotly as plotly_widgets
|
| 4 |
+
import plotly.graph_objects as go
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
GRID_SIZES = [16, 32, 64, 128, 256, 512]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _ensure_state_defaults(state):
|
| 11 |
+
try:
|
| 12 |
+
state.update({
|
| 13 |
+
"qlbm_initialized": True,
|
| 14 |
+
"logo_src": getattr(state, "logo_src", None),
|
| 15 |
+
"is_running": False,
|
| 16 |
+
"simulation_has_run": False,
|
| 17 |
+
"error_message": "",
|
| 18 |
+
"geometry_selection": getattr(state, "geometry_selection", None),
|
| 19 |
+
"lbm_dim": getattr(state, "lbm_dim", "2D"),
|
| 20 |
+
"domain_L": getattr(state, "domain_L", 1.0),
|
| 21 |
+
"domain_W": getattr(state, "domain_W", 1.0),
|
| 22 |
+
"domain_H": getattr(state, "domain_H", 1.0),
|
| 23 |
+
"dist_type": getattr(state, "dist_type", None),
|
| 24 |
+
"advecting_field": getattr(state, "advecting_field", None),
|
| 25 |
+
"inlet_velocity": getattr(state, "inlet_velocity", 1.0),
|
| 26 |
+
"inlet_temperature": getattr(state, "inlet_temperature", 300.0),
|
| 27 |
+
"nx_slider_index": getattr(state, "nx_slider_index", None),
|
| 28 |
+
"nx": getattr(state, "nx", None),
|
| 29 |
+
"output_type": getattr(state, "output_type", "Surface Plot"),
|
| 30 |
+
})
|
| 31 |
+
except Exception:
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _build_placeholder_figure(state):
|
| 36 |
+
try:
|
| 37 |
+
if state.geometry_selection == "Rectangular domain with a heated box (2D/3D)" and str(state.lbm_dim) == "3D":
|
| 38 |
+
fig = go.Figure(data=[go.Scatter3d(x=[0, 1], y=[0, 1], z=[0, 1], mode="markers")])
|
| 39 |
+
fig.update_layout(height=560, margin=dict(l=10, r=10, t=30, b=10))
|
| 40 |
+
return fig
|
| 41 |
+
fig = go.Figure(data=go.Heatmap(z=[[0, 1], [1, 0]], colorscale="RdBu"))
|
| 42 |
+
fig.update_layout(height=560, margin=dict(l=10, r=10, t=30, b=10))
|
| 43 |
+
return fig
|
| 44 |
+
except Exception:
|
| 45 |
+
return go.Figure()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def build(server):
|
| 49 |
+
state, ctrl = server.state, server.controller
|
| 50 |
+
_ensure_state_defaults(state)
|
| 51 |
+
|
| 52 |
+
def run_simulation():
|
| 53 |
+
state.is_running = True
|
| 54 |
+
state.error_message = ""
|
| 55 |
+
try:
|
| 56 |
+
fig = _build_placeholder_figure(state)
|
| 57 |
+
try:
|
| 58 |
+
ctrl.qlbm_plot_update(fig)
|
| 59 |
+
except Exception:
|
| 60 |
+
pass
|
| 61 |
+
state.simulation_has_run = True
|
| 62 |
+
except Exception as e:
|
| 63 |
+
state.error_message = f"Run failed: {e}"
|
| 64 |
+
finally:
|
| 65 |
+
state.is_running = False
|
| 66 |
+
|
| 67 |
+
def reset_all():
|
| 68 |
+
try:
|
| 69 |
+
state.update({
|
| 70 |
+
"geometry_selection": None,
|
| 71 |
+
"lbm_dim": "2D",
|
| 72 |
+
"domain_L": 1.0,
|
| 73 |
+
"domain_W": 1.0,
|
| 74 |
+
"domain_H": 1.0,
|
| 75 |
+
"dist_type": None,
|
| 76 |
+
"advecting_field": None,
|
| 77 |
+
"inlet_velocity": 1.0,
|
| 78 |
+
"inlet_temperature": 300.0,
|
| 79 |
+
"nx_slider_index": None,
|
| 80 |
+
"nx": None,
|
| 81 |
+
"output_type": "Surface Plot",
|
| 82 |
+
"is_running": False,
|
| 83 |
+
"simulation_has_run": False,
|
| 84 |
+
"error_message": "",
|
| 85 |
+
})
|
| 86 |
+
try:
|
| 87 |
+
ctrl.qlbm_plot_update(go.Figure())
|
| 88 |
+
except Exception:
|
| 89 |
+
pass
|
| 90 |
+
except Exception:
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
@state.change("nx_slider_index")
|
| 94 |
+
def _on_nx_index_change(nx_slider_index, **_):
|
| 95 |
+
try:
|
| 96 |
+
state.nx = GRID_SIZES[int(nx_slider_index)] if nx_slider_index is not None else None
|
| 97 |
+
except Exception:
|
| 98 |
+
state.nx = None
|
| 99 |
+
|
| 100 |
+
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
|
| 101 |
+
with vuetify3.VRow(no_gutters=True, classes="fill-height"):
|
| 102 |
+
with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
|
| 103 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 104 |
+
vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
|
| 105 |
+
with vuetify3.VCardText():
|
| 106 |
+
vuetify3.VDivider(classes="my-2")
|
| 107 |
+
vuetify3.VCardSubtitle("Problems", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 108 |
+
vuetify3.VList(density="compact", lines="one", items=(
|
| 109 |
+
"qlbm_problems",
|
| 110 |
+
[
|
| 111 |
+
{"title": "1. Scalar advection-diffusion in a box"},
|
| 112 |
+
{"title": "2. Laminar flow & heat transfer for a heated body in water."},
|
| 113 |
+
],
|
| 114 |
+
))
|
| 115 |
+
vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 116 |
+
vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
|
| 117 |
+
vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 118 |
+
vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
|
| 119 |
+
vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 120 |
+
vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
|
| 121 |
+
|
| 122 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 123 |
+
vuetify3.VCardTitle("Geometry", classes="text-primary")
|
| 124 |
+
with vuetify3.VCardText():
|
| 125 |
+
vuetify3.VSelect(
|
| 126 |
+
label="Select",
|
| 127 |
+
v_model=("geometry_selection", None),
|
| 128 |
+
items=(
|
| 129 |
+
"geometry_options",
|
| 130 |
+
[
|
| 131 |
+
"None",
|
| 132 |
+
"Free space",
|
| 133 |
+
"Rectangular domain with a heated box (2D/3D)",
|
| 134 |
+
],
|
| 135 |
+
),
|
| 136 |
+
placeholder="Select",
|
| 137 |
+
density="compact",
|
| 138 |
+
color="primary",
|
| 139 |
+
)
|
| 140 |
+
with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (2D/3D)'", classes="pa-0 mt-2"):
|
| 141 |
+
vuetify3.VRadioGroup(v_model=("lbm_dim", "2D"), row=True, density="compact", color="primary", children=[
|
| 142 |
+
vuetify3.VRadio(label="2D", value="2D"),
|
| 143 |
+
vuetify3.VRadio(label="3D", value="3D"),
|
| 144 |
+
])
|
| 145 |
+
with vuetify3.VRow(dense=True):
|
| 146 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 147 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Width (W)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 148 |
+
vuetify3.VTextField(v_if="lbm_dim === '3D'", label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")
|
| 149 |
+
|
| 150 |
+
with vuetify3.VCard(v_if="geometry_selection === 'Rectangular domain with a heated box (2D/3D)'", classes="mb-4"):
|
| 151 |
+
vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
|
| 152 |
+
with vuetify3.VCardText():
|
| 153 |
+
vuetify3.VSelect(label="Initial Condition", v_model=("dist_type", None), items=("dist_type_opts", ["None", "Delta", "Gaussian"]), density="compact", color="primary")
|
| 154 |
+
vuetify3.VSelect(label="Advecting field", v_model=("advecting_field", None), items=("advect_fields", ["Uniform", "Swirl", "Shear", "TGV"]), density="compact", color="primary", classes="mt-2")
|
| 155 |
+
with vuetify3.VRow(dense=True, classes="mt-2"):
|
| 156 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet flow velocity", v_model=("inlet_velocity", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 157 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet temperature (K)", v_model=("inlet_temperature", 300.0), type="number", step="1", density="compact", color="primary")])
|
| 158 |
+
|
| 159 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 160 |
+
vuetify3.VCardTitle("Meshing", classes="text-primary")
|
| 161 |
+
with vuetify3.VCardText():
|
| 162 |
+
with vuetify3.VSlider(
|
| 163 |
+
v_model=("nx_slider_index", None),
|
| 164 |
+
label="No. of points per direction:",
|
| 165 |
+
min=0,
|
| 166 |
+
max=len(GRID_SIZES) - 1,
|
| 167 |
+
step=1,
|
| 168 |
+
show_ticks="always",
|
| 169 |
+
thumb_label="always",
|
| 170 |
+
density="compact",
|
| 171 |
+
color="primary",
|
| 172 |
+
):
|
| 173 |
+
vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512][modelValue] }}"])
|
| 174 |
+
|
| 175 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 176 |
+
vuetify3.VCardTitle("Backends", classes="text-primary")
|
| 177 |
+
with vuetify3.VCardText():
|
| 178 |
+
vuetify3.VAlert(type="info", color="primary", variant="tonal", density="compact", children=["Simulator only (placeholder)"])
|
| 179 |
+
|
| 180 |
+
with vuetify3.VRow(dense=True, classes="mb-2"):
|
| 181 |
+
vuetify3.VCol(children=[
|
| 182 |
+
vuetify3.VBtn(text=("Run Simulation"), color="primary", block=True, disabled=("is_running || !geometry_selection || (geometry_selection === 'Rectangular domain with a heated box (2D/3D)' && nx === null)", False), click=run_simulation)
|
| 183 |
+
])
|
| 184 |
+
vuetify3.VCol(children=[
|
| 185 |
+
vuetify3.VBtn(text=("Reset"), color="secondary", variant="tonal", block=True, click=reset_all)
|
| 186 |
+
])
|
| 187 |
+
|
| 188 |
+
with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
|
| 189 |
+
with vuetify3.VCard(v_if="simulation_has_run", classes="mb-4"):
|
| 190 |
+
vuetify3.VCardSubtitle("Output Configuration", classes="text-primary")
|
| 191 |
+
with vuetify3.VCardText():
|
| 192 |
+
with vuetify3.VRadioGroup(v_model=("output_type", "Surface Plot"), row=True, density="compact", color="primary"):
|
| 193 |
+
vuetify3.VRadio(label="Surface", value="Surface Plot")
|
| 194 |
+
vuetify3.VRadio(label="Line Sampling", value="Line Sampling")
|
| 195 |
+
|
| 196 |
+
with vuetify3.VCard(classes="flex-grow-1", style="min-height: 0;"):
|
| 197 |
+
with vuetify3.VContainer(v_if="is_running", fluid=True, classes="fill-height d-flex flex-column align-center justify-center"):
|
| 198 |
+
vuetify3.VProgressCircular(indeterminate=True, size=64, color="primary")
|
| 199 |
+
vuetify3.VCardSubtitle("Running simulation...", classes="mt-4")
|
| 200 |
+
with vuetify3.VContainer(v_if="!is_running", fluid=True, classes="fill-height pa-2"):
|
| 201 |
+
fig = plotly_widgets.Figure(figure=go.Figure(), responsive=True, style="width: 100%; min-height: 560px;")
|
| 202 |
+
ctrl.qlbm_plot_update = fig.update
|
| 203 |
+
vuetify3.VContainer(v_if="!simulation_has_run", classes="d-flex align-center justify-center", style="height: 360px; color: rgba(0,0,0,.6);", children=["Configure inputs and run to display results."])
|
qlbm.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
import re # Added missing import for regex usage
|
| 5 |
+
from trame.app import get_server
|
| 6 |
+
from trame_vuetify.ui.vuetify3 import SinglePageLayout
|
| 7 |
+
from trame_vuetify.widgets import vuetify3
|
| 8 |
+
from trame.widgets import html as trame_html
|
| 9 |
+
from trame_plotly.widgets import plotly as plotly_widgets
|
| 10 |
+
import plotly.graph_objects as go
|
| 11 |
+
|
| 12 |
+
GRID_SIZES = [16, 32, 64, 128, 256, 512, 1024, 2048]
|
| 13 |
+
|
| 14 |
+
def load_logo_data_uri():
|
| 15 |
+
try:
|
| 16 |
+
base_dir = os.path.dirname(__file__)
|
| 17 |
+
for name in [
|
| 18 |
+
"ansys-part-of-synopsys-logo.svg",
|
| 19 |
+
"synopsys-logo-color-rgb.svg",
|
| 20 |
+
"synopsys-logo-color-rgb.png",
|
| 21 |
+
"synopsys-logo-color-rgb.jpg",
|
| 22 |
+
]:
|
| 23 |
+
p = os.path.join(base_dir, name)
|
| 24 |
+
if os.path.exists(p):
|
| 25 |
+
ext = os.path.splitext(p)[1].lower()
|
| 26 |
+
mime = "image/svg+xml" if ext == ".svg" else ("image/png" if ext == ".png" else "image/jpeg")
|
| 27 |
+
import base64
|
| 28 |
+
with open(p, "rb") as f:
|
| 29 |
+
b64 = base64.b64encode(f.read()).decode("ascii")
|
| 30 |
+
return f"data:{mime};base64,{b64}"
|
| 31 |
+
except Exception:
|
| 32 |
+
pass
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
def update_qubit_3D_info(grid_size: int):
|
| 36 |
+
try:
|
| 37 |
+
num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
|
| 38 |
+
total_qubits = 3 * num_reg_qubits + 3
|
| 39 |
+
grid_display = f"Grid Size: {grid_size} × {grid_size} × {grid_size}"
|
| 40 |
+
x = np.array([16, 32, 64, 128, 256])
|
| 41 |
+
y = np.log2(x).astype(int)
|
| 42 |
+
fig = go.Figure()
|
| 43 |
+
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction', line=dict(color='#7A3DB5', width=3)))
|
| 44 |
+
fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
|
| 45 |
+
fig.update_layout(xaxis_title="Grid Size (Points/Direction)", yaxis_title="Qubits/Direction", width=616, height=320, margin=dict(l=40, r=20, t=20, b=40))
|
| 46 |
+
warning = "⚠️ Warning: Grid sizes > 64 may exceed simulator/memory limits!" if grid_size > 64 else ""
|
| 47 |
+
return fig, grid_display, total_qubits, warning
|
| 48 |
+
except Exception:
|
| 49 |
+
return go.Figure(), "Grid Size: N/A", 0, ""
|
| 50 |
+
|
| 51 |
+
# Controllers (created per server instance)
|
| 52 |
+
|
| 53 |
+
def _make_controllers(server):
|
| 54 |
+
state, ctrl = server.state, server.controller
|
| 55 |
+
|
| 56 |
+
def run_simulation():
|
| 57 |
+
state.is_running = True
|
| 58 |
+
state.error_message = ""
|
| 59 |
+
try:
|
| 60 |
+
state.simulation_has_run = True
|
| 61 |
+
except Exception as e:
|
| 62 |
+
state.error_message = f"Run failed: {e}"
|
| 63 |
+
finally:
|
| 64 |
+
state.is_running = False
|
| 65 |
+
|
| 66 |
+
def reset_all():
|
| 67 |
+
state.update({
|
| 68 |
+
"problems_selection": None,
|
| 69 |
+
"geometry_selection": None,
|
| 70 |
+
"domain_L": 1.0,
|
| 71 |
+
"domain_W": 1.0,
|
| 72 |
+
"domain_H": 1.0,
|
| 73 |
+
"dist_type": None,
|
| 74 |
+
"impulse_x": 0.5,
|
| 75 |
+
"impulse_y": 0.5,
|
| 76 |
+
"peak_pair": "(0.5, 0.5)",
|
| 77 |
+
"mu_x": 0.5,
|
| 78 |
+
"mu_y": 0.5,
|
| 79 |
+
"sigma_x": 0.25,
|
| 80 |
+
"sigma_y": 0.15,
|
| 81 |
+
"mu_pair": "(0.5, 0.5)",
|
| 82 |
+
"excitation_error_message": "",
|
| 83 |
+
"advecting_field": None,
|
| 84 |
+
"inlet_velocity": 1.0,
|
| 85 |
+
"inlet_temperature": 300.0,
|
| 86 |
+
"nx_slider_index": None,
|
| 87 |
+
"nx": None,
|
| 88 |
+
"output_type": "Surface Plot",
|
| 89 |
+
"is_running": False,
|
| 90 |
+
"simulation_has_run": False,
|
| 91 |
+
"error_message": "",
|
| 92 |
+
"backend_type": "Simulator",
|
| 93 |
+
"selected_simulator": "IBM Qiskit simulator",
|
| 94 |
+
"selected_qpu": "IBM QPU",
|
| 95 |
+
"qubit_grid_info": "",
|
| 96 |
+
"qubit_warning": "",
|
| 97 |
+
})
|
| 98 |
+
|
| 99 |
+
ctrl.run_qlbm_sim = run_simulation
|
| 100 |
+
ctrl.reset_qlbm = reset_all
|
| 101 |
+
|
| 102 |
+
def on_nx_index_change(nx_slider_index, **_):
|
| 103 |
+
try:
|
| 104 |
+
state.nx = GRID_SIZES[int(nx_slider_index)] if nx_slider_index is not None else None
|
| 105 |
+
except Exception:
|
| 106 |
+
state.nx = None
|
| 107 |
+
|
| 108 |
+
def update_qubit_plot(nx, **_):
|
| 109 |
+
try:
|
| 110 |
+
if nx is None:
|
| 111 |
+
state.qubit_grid_info = ""
|
| 112 |
+
state.qubit_warning = ""
|
| 113 |
+
return
|
| 114 |
+
fig, grid_info, _, warning = update_qubit_3D_info(nx)
|
| 115 |
+
state.qubit_grid_info = grid_info
|
| 116 |
+
state.qubit_warning = warning
|
| 117 |
+
if hasattr(ctrl, 'qlbm_qubit_update'):
|
| 118 |
+
ctrl.qlbm_qubit_update(fig)
|
| 119 |
+
except Exception:
|
| 120 |
+
pass
|
| 121 |
+
|
| 122 |
+
def sync_peak_pair(peak_pair, **_):
|
| 123 |
+
try:
|
| 124 |
+
m = re.match(r"\(\s*([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*\)", str(peak_pair))
|
| 125 |
+
if not m:
|
| 126 |
+
raise ValueError
|
| 127 |
+
state.impulse_x = max(0.0, min(1.0, float(m.group(1))))
|
| 128 |
+
state.impulse_y = max(0.0, min(1.0, float(m.group(2))))
|
| 129 |
+
state.excitation_error_message = ""
|
| 130 |
+
except Exception:
|
| 131 |
+
state.excitation_error_message = "Invalid Peak. Use (x, y) in [0,1]."
|
| 132 |
+
|
| 133 |
+
def sync_mu_pair(mu_pair, **_):
|
| 134 |
+
try:
|
| 135 |
+
m = re.match(r"\(\s*([-+]?[0-9]*\.?[0-9]+)\s*,\s*([-+]?[0-9]*\.?[0-9]+)\s*\)", str(mu_pair))
|
| 136 |
+
if not m:
|
| 137 |
+
raise ValueError
|
| 138 |
+
state.mu_x = max(0.0, min(1.0, float(m.group(1))))
|
| 139 |
+
state.mu_y = max(0.0, min(1.0, float(m.group(2))))
|
| 140 |
+
state.excitation_error_message = ""
|
| 141 |
+
except Exception:
|
| 142 |
+
state.excitation_error_message = "Invalid Mu. Use (x, y) in [0,1]."
|
| 143 |
+
|
| 144 |
+
# Register watchers
|
| 145 |
+
server.state.change("nx_slider_index")(on_nx_index_change)
|
| 146 |
+
server.state.change("nx")(update_qubit_plot)
|
| 147 |
+
server.state.change("peak_pair")(sync_peak_pair)
|
| 148 |
+
server.state.change("mu_pair")(sync_mu_pair)
|
| 149 |
+
|
| 150 |
+
# Initialize defaults once per server
|
| 151 |
+
|
| 152 |
+
def _init_state(server):
|
| 153 |
+
state = server.state
|
| 154 |
+
if getattr(state, "qlbm_initialized", False):
|
| 155 |
+
return
|
| 156 |
+
state.update({
|
| 157 |
+
"logo_src": load_logo_data_uri(),
|
| 158 |
+
"is_running": False,
|
| 159 |
+
"simulation_has_run": False,
|
| 160 |
+
"error_message": "",
|
| 161 |
+
"problems_selection": None,
|
| 162 |
+
"geometry_selection": None,
|
| 163 |
+
"domain_L": 1.0,
|
| 164 |
+
"domain_W": 1.0,
|
| 165 |
+
"domain_H": 1.0,
|
| 166 |
+
"dist_type": None,
|
| 167 |
+
"impulse_x": 0.5,
|
| 168 |
+
"impulse_y": 0.5,
|
| 169 |
+
"peak_pair": "(0.5, 0.5)",
|
| 170 |
+
"mu_x": 0.5,
|
| 171 |
+
"mu_y": 0.5,
|
| 172 |
+
"sigma_x": 0.25,
|
| 173 |
+
"sigma_y": 0.15,
|
| 174 |
+
"mu_pair": "(0.5, 0.5)",
|
| 175 |
+
"excitation_error_message": "",
|
| 176 |
+
"advecting_field": None,
|
| 177 |
+
"inlet_velocity": 1.0,
|
| 178 |
+
"inlet_temperature": 300.0,
|
| 179 |
+
"nx_slider_index": None,
|
| 180 |
+
"nx": None,
|
| 181 |
+
"backend_type": "Simulator",
|
| 182 |
+
"selected_simulator": "IBM Qiskit simulator",
|
| 183 |
+
"selected_qpu": "IBM QPU",
|
| 184 |
+
"output_type": "Surface Plot",
|
| 185 |
+
"qubit_grid_info": "",
|
| 186 |
+
"qubit_warning": "",
|
| 187 |
+
"qlbm_initialized": True,
|
| 188 |
+
})
|
| 189 |
+
|
| 190 |
+
# Public build(server) used by multipage app
|
| 191 |
+
|
| 192 |
+
def build(server):
|
| 193 |
+
_init_state(server)
|
| 194 |
+
_make_controllers(server)
|
| 195 |
+
state, ctrl = server.state, server.controller
|
| 196 |
+
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
|
| 197 |
+
with vuetify3.VRow(no_gutters=True, classes="fill-height"):
|
| 198 |
+
# Left column
|
| 199 |
+
with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
|
| 200 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 201 |
+
vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
|
| 202 |
+
with vuetify3.VCardText():
|
| 203 |
+
vuetify3.VDivider(classes="my-2")
|
| 204 |
+
vuetify3.VCardSubtitle("Problems!", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 205 |
+
vuetify3.VSelect(label="Select a problem", v_model=("problems_selection", None), items=("qlbm_problems", ["1. Scalar advection-diffusion in a box", "2. Laminar flow & heat transfer for a heated body in water."]), placeholder="Select", density="compact", color="primary")
|
| 206 |
+
vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 207 |
+
vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
|
| 208 |
+
vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 209 |
+
vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
|
| 210 |
+
vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
|
| 211 |
+
vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
|
| 212 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 213 |
+
vuetify3.VCardTitle("Geometry", classes="text-primary")
|
| 214 |
+
with vuetify3.VCardText():
|
| 215 |
+
vuetify3.VSelect(label="Select", v_model=("geometry_selection", None), items=("geometry_options", ["Free space", "Rectangular domain with a heated box (3D)"]), placeholder="Select", density="compact", color="primary")
|
| 216 |
+
with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="pa-0 mt-2"):
|
| 217 |
+
with vuetify3.VRow(dense=True):
|
| 218 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 219 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Breadth (B)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 220 |
+
vuetify3.VTextField(label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")
|
| 221 |
+
with vuetify3.VCard(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="mb-4"):
|
| 222 |
+
vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
|
| 223 |
+
with vuetify3.VCardText():
|
| 224 |
+
vuetify3.VTextField(v_model=("mu_pair", "(0.5, 0.5)"), label="Gaussian μ (x, y) in [0,1]", density="compact", color="primary")
|
| 225 |
+
with vuetify3.VRow(dense=True, classes="mt-1"):
|
| 226 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Sigma X (0–1)", v_model=("sigma_x", 0.25), type="number", step="0.01", density="compact", color="primary")])
|
| 227 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Sigma Y (0–1)", v_model=("sigma_y", 0.15), type="number", step="0.01", density="compact", color="primary")])
|
| 228 |
+
vuetify3.VAlert(v_if="excitation_error_message", type="error", variant="tonal", density="compact", children=["{{ excitation_error_message }}"], classes="mt-2")
|
| 229 |
+
vuetify3.VSelect(label="Advecting field", v_model=("advecting_field", None), items=("advect_fields", ["Uniform", "Swirl", "Shear", "TGV"]), density="compact", color="primary", classes="mt-2")
|
| 230 |
+
with vuetify3.VRow(dense=True, classes="mt-2"):
|
| 231 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet flow velocity", v_model=("inlet_velocity", 1.0), type="number", step="0.1", density="compact", color="primary")])
|
| 232 |
+
vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet temperature (K)", v_model=("inlet_temperature", 300.0), type="number", step="1", density="compact", color="primary")])
|
| 233 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 234 |
+
vuetify3.VCardTitle("Meshing", classes="text-primary")
|
| 235 |
+
with vuetify3.VCardText():
|
| 236 |
+
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=False, location="end"):
|
| 237 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 238 |
+
with vuetify3.VSlider(v_bind="props", v_model=("nx_slider_index", None), label="No. of points per direction:", min=0, max=len(GRID_SIZES)-1, step=1, show_ticks="always", thumb_label="always", density="compact", color="primary"):
|
| 239 |
+
vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512, 1024, 2048][modelValue] }}"])
|
| 240 |
+
with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 700px;"):
|
| 241 |
+
qubit_fig_widget = plotly_widgets.Figure(figure=go.Figure(), responsive=True, style="width: 616px; height: 320px; min-height: 320px;")
|
| 242 |
+
ctrl.qlbm_qubit_update = qubit_fig_widget.update
|
| 243 |
+
vuetify3.VCardText(children=["{{ qubit_grid_info }}", "{{ qubit_warning }}"], classes="text-caption mt-2")
|
| 244 |
+
with vuetify3.VCard(classes="mb-4"):
|
| 245 |
+
vuetify3.VCardTitle("Backends", classes="text-primary")
|
| 246 |
+
with vuetify3.VCardText():
|
| 247 |
+
with vuetify3.VRow(dense=True, classes="mb-2"):
|
| 248 |
+
with vuetify3.VCol():
|
| 249 |
+
vuetify3.VAlert(type="info", color="primary", variant="tonal", density="compact", children=["Selected: ", "{{ backend_type || '—' }}", " - ", "{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}"])
|
| 250 |
+
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
|
| 251 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 252 |
+
vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
|
| 253 |
+
with vuetify3.VList(density="compact"):
|
| 254 |
+
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
|
| 255 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 256 |
+
vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
|
| 257 |
+
with vuetify3.VList(density="compact"):
|
| 258 |
+
vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
|
| 259 |
+
vuetify3.VListItem(title="IonQ simulator", click="backend_type = 'Simulator'; selected_simulator = 'IonQ simulator'")
|
| 260 |
+
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
|
| 261 |
+
with vuetify3.Template(v_slot_activator="{ props }"):
|
| 262 |
+
vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
|
| 263 |
+
with vuetify3.VList(density="compact"):
|
| 264 |
+
vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
|
| 265 |
+
vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
|
| 266 |
+
with vuetify3.VRow(dense=True, classes="gap-2 mt-4"):
|
| 267 |
+
with vuetify3.VCol():
|
| 268 |
+
vuetify3.VBtn(text="Run Simulation", color="primary", block=True, disabled=("is_running || !geometry_selection || (geometry_selection === 'Rectangular domain with a heated box (3D)' && nx === null)", False), click=ctrl.run_qlbm_sim)
|
| 269 |
+
with vuetify3.VCol():
|
| 270 |
+
vuetify3.VBtn(text="Reset", color="secondary", variant="tonal", block=True, click=ctrl.reset_qlbm)
|
| 271 |
+
with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
|
| 272 |
+
with vuetify3.VContainer(fluid=True, classes="fill-height d-flex align-center justify-center"):
|
| 273 |
+
vuetify3.VCardText("Select a geometry and configure inputs to display results.", classes="text-center text-medium-emphasis")
|
| 274 |
+
|
| 275 |
+
# Standalone execution
|
| 276 |
+
if __name__ == "__main__":
|
| 277 |
+
from trame.app import get_server
|
| 278 |
+
from trame_vuetify.ui.vuetify3 import SinglePageLayout
|
| 279 |
+
_server = get_server()
|
| 280 |
+
# NOTE: build must run inside a layout/content context; do not call before layout
|
| 281 |
+
with SinglePageLayout(_server) as _layout:
|
| 282 |
+
_layout.title.set_text("QLBM: Lattice Boltzmann")
|
| 283 |
+
with _layout.content:
|
| 284 |
+
build(_server)
|
| 285 |
+
_server.start(open_browser=True)
|
synopsys-logo-color-rgb.png
DELETED
|
Binary file (22.1 kB)
|
|
|
synopsys-logo-color-rgb.svg
DELETED
utils/__pycache__/base_functions.cpython-311.pyc
ADDED
|
Binary file (22.3 kB). View file
|
|
|
utils/__pycache__/base_functions.cpython-313.pyc
ADDED
|
Binary file (18.6 kB). View file
|
|
|
utils/__pycache__/delta_impulse_generator.cpython-311.pyc
ADDED
|
Binary file (29.3 kB). View file
|
|
|
utils/__pycache__/delta_impulse_generator.cpython-313.pyc
ADDED
|
Binary file (25.1 kB). View file
|
|
|
utils/base_functions.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import scipy.sparse as sp
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
from scipy.special import jn
|
| 7 |
+
from scipy.sparse import identity, csr_matrix, kron, diags, eye
|
| 8 |
+
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
|
| 9 |
+
from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate
|
| 10 |
+
from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli
|
| 11 |
+
from scipy.linalg import expm
|
| 12 |
+
# from tools import *
|
| 13 |
+
from qiskit.qasm3 import dumps # QASM 3 exporter
|
| 14 |
+
from qiskit.qasm3 import loads
|
| 15 |
+
from qiskit.circuit.library import QFT
|
| 16 |
+
from qiskit.primitives import StatevectorEstimator
|
| 17 |
+
from qiskit import transpile
|
| 18 |
+
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit, apply_circuit_to_state, compute_overlap
|
| 19 |
+
from qiskit_aer import AerSimulator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
simulator_settings = AerSimulator(
|
| 23 |
+
method="matrix_product_state",
|
| 24 |
+
matrix_product_state_max_bond_dimension=100,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
def Wj(j, theta, lam, name='Wj', xgate=False):
|
| 28 |
+
if not xgate:
|
| 29 |
+
name = f' $W_{j}$ '
|
| 30 |
+
qc=QuantumCircuit(j, name=name)
|
| 31 |
+
|
| 32 |
+
if j > 1:
|
| 33 |
+
qc.cx(j-1, range(j-1))
|
| 34 |
+
if lam != 0:
|
| 35 |
+
qc.p(lam, j-1)
|
| 36 |
+
qc.h(j-1)
|
| 37 |
+
if xgate:
|
| 38 |
+
qc.x(range(j-1))
|
| 39 |
+
|
| 40 |
+
# the multicontrolled rz gate
|
| 41 |
+
# it will be decomposed in qiskit
|
| 42 |
+
if j > 1:
|
| 43 |
+
qc.mcrz(theta, range(j-1), j-1)
|
| 44 |
+
else:
|
| 45 |
+
qc.rz(theta, j-1)
|
| 46 |
+
|
| 47 |
+
if xgate:
|
| 48 |
+
qc.x(range(j-1))
|
| 49 |
+
qc.h(j-1)
|
| 50 |
+
if lam != 0:
|
| 51 |
+
qc.p(-lam, j-1)
|
| 52 |
+
if j > 1:
|
| 53 |
+
qc.cx(j-1, range(j-1))
|
| 54 |
+
|
| 55 |
+
return qc
|
| 56 |
+
|
| 57 |
+
def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
|
| 58 |
+
if not xgate:
|
| 59 |
+
name = f' $W_{j}_block$ '
|
| 60 |
+
qc=QuantumCircuit(n + j, name=name)
|
| 61 |
+
|
| 62 |
+
if j > 1:
|
| 63 |
+
qc.cx(n + j-1, range(n, n+j-1))
|
| 64 |
+
if lam != 0:
|
| 65 |
+
qc.p(lam, n + j -1)
|
| 66 |
+
qc.h(n + j -1)
|
| 67 |
+
|
| 68 |
+
if xgate and j>1:
|
| 69 |
+
if isinstance(xgate, (list, tuple)): # selective application
|
| 70 |
+
for idx, flag in enumerate(xgate):
|
| 71 |
+
if flag: # only apply where flag == 1
|
| 72 |
+
qc.x(n + idx)
|
| 73 |
+
elif xgate is True: # apply to all
|
| 74 |
+
qc.x(range(n, n+j-1))
|
| 75 |
+
|
| 76 |
+
# the multicontrolled rz gate
|
| 77 |
+
# it will be decomposed in qiskit
|
| 78 |
+
if j > 1:
|
| 79 |
+
mcrz = RZGate(theta).control(len(ctrl_state) + j-1, ctrl_state = "1"*(j-1)+ctrl_state)
|
| 80 |
+
qc.append(mcrz, range(0, n + j))
|
| 81 |
+
else:
|
| 82 |
+
mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state = ctrl_state)
|
| 83 |
+
qc.append(mcrz, range(0, n+j))
|
| 84 |
+
|
| 85 |
+
if xgate and j>1:
|
| 86 |
+
if isinstance(xgate, (list, tuple)): # selective application
|
| 87 |
+
for idx, flag in enumerate(xgate):
|
| 88 |
+
if flag: # only apply where flag == 1
|
| 89 |
+
qc.x(n + idx)
|
| 90 |
+
elif xgate is True: # apply to all
|
| 91 |
+
qc.x(range(n, n+j-1))
|
| 92 |
+
|
| 93 |
+
qc.h(n+ j-1)
|
| 94 |
+
if lam != 0:
|
| 95 |
+
qc.p(-lam, n + j-1)
|
| 96 |
+
if j > 1:
|
| 97 |
+
qc.cx(n + j-1, range(n, n +j-1))
|
| 98 |
+
|
| 99 |
+
return qc.to_gate(label=name)
|
| 100 |
+
|
| 101 |
+
def V1(nx, dt, name = "V1"):
|
| 102 |
+
n = int(np.ceil(np.log2(nx)))
|
| 103 |
+
|
| 104 |
+
derivatives = QuantumRegister(2*n)
|
| 105 |
+
blocks = QuantumRegister(2)
|
| 106 |
+
|
| 107 |
+
qc = QuantumCircuit(derivatives, blocks)
|
| 108 |
+
|
| 109 |
+
W1 = Wj_block(2, n, "0"*n, -dt , 0, xgate=True)
|
| 110 |
+
qc.append(W1, list(derivatives[0:n])+list(blocks[:]))
|
| 111 |
+
|
| 112 |
+
# qc.barrier()
|
| 113 |
+
|
| 114 |
+
W2 = Wj_block(3, n-1, "1"*(n-1), dt , 0, xgate=[0,1])
|
| 115 |
+
qc.append(W2, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
|
| 116 |
+
|
| 117 |
+
# qc.barrier()
|
| 118 |
+
|
| 119 |
+
W3 = Wj_block(1, n+1, "0"*(n+1), dt , 0, xgate=False)
|
| 120 |
+
qc.append(W3, list(derivatives[n:2*n])+list(blocks[:]))
|
| 121 |
+
|
| 122 |
+
# qc.barrier()
|
| 123 |
+
|
| 124 |
+
W4 = Wj_block(2, n, "0"+"1"*(n-1), -dt , 0, xgate=False)
|
| 125 |
+
qc.append(W4, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
|
| 126 |
+
|
| 127 |
+
return qc
|
| 128 |
+
|
| 129 |
+
def V2(nx, dt, name = "V2"):
|
| 130 |
+
n = int(np.ceil(np.log2(nx)))
|
| 131 |
+
|
| 132 |
+
derivatives = QuantumRegister(2*n)
|
| 133 |
+
blocks = QuantumRegister(2)
|
| 134 |
+
|
| 135 |
+
qc = QuantumCircuit(derivatives, blocks)
|
| 136 |
+
|
| 137 |
+
W1 = Wj_block(2, 0, "", -2*dt , -np.pi/2, xgate=True)
|
| 138 |
+
qc.append(W1, list(blocks[:]))
|
| 139 |
+
|
| 140 |
+
# qc.barrier()
|
| 141 |
+
|
| 142 |
+
for j in range(1, n+1):
|
| 143 |
+
W2 = Wj_block(2+j, 0, "", 2*dt , -np.pi/2, xgate=[1]*(j-1)+[0,1])
|
| 144 |
+
qc.append(W2, list(derivatives[0:j])+list(blocks[:]))
|
| 145 |
+
|
| 146 |
+
# qc.barrier()
|
| 147 |
+
|
| 148 |
+
W3 = Wj_block(2, n, "0"*n, -dt , -np.pi/2, xgate=True)
|
| 149 |
+
qc.append(W3, list(derivatives[0:n])+list(blocks[:]))
|
| 150 |
+
|
| 151 |
+
# qc.barrier()
|
| 152 |
+
|
| 153 |
+
W4 = Wj_block(2, n, "1"*n, 2*dt , -np.pi/2, xgate=True)
|
| 154 |
+
qc.append(W4, list(derivatives[0:n])+list(blocks[:]))
|
| 155 |
+
|
| 156 |
+
# qc.barrier()
|
| 157 |
+
|
| 158 |
+
W5 = Wj_block(3, n-1, "1"*(n-1), dt , -np.pi/2, xgate=[0,1])
|
| 159 |
+
qc.append(W5, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
|
| 160 |
+
|
| 161 |
+
# qc.barrier()
|
| 162 |
+
|
| 163 |
+
W6 = Wj_block(1, 1, "0", 2*dt , -np.pi/2, xgate=False)
|
| 164 |
+
qc.append(W6, list(blocks[:]))
|
| 165 |
+
|
| 166 |
+
# qc.barrier()
|
| 167 |
+
|
| 168 |
+
for j in range(1, n+1):
|
| 169 |
+
W7 = Wj_block(1+j, 1, "0", -2*dt , -np.pi/2, xgate=[1]*(j-1))
|
| 170 |
+
qc.append(W7, [blocks[0]]+list(derivatives[n:n+j])+[blocks[1]])
|
| 171 |
+
|
| 172 |
+
# qc.barrier()
|
| 173 |
+
|
| 174 |
+
W8 = Wj_block(1, n+1, "0"*(n+1), dt , -np.pi/2, xgate=False)
|
| 175 |
+
qc.append(W8, list(derivatives[n:2*n])+list(blocks[:]))
|
| 176 |
+
|
| 177 |
+
# qc.barrier()
|
| 178 |
+
|
| 179 |
+
W9 = Wj_block(1, n+1, "0"+"1"*(n), -2*dt , -np.pi/2, xgate=False)
|
| 180 |
+
qc.append(W9, list(derivatives[n:2*n])+list(blocks[:]))
|
| 181 |
+
|
| 182 |
+
# qc.barrier()
|
| 183 |
+
|
| 184 |
+
W10 = Wj_block(2, n, "0"+"1"*(n-1), -dt , -np.pi/2, xgate=False)
|
| 185 |
+
qc.append(W10, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
|
| 186 |
+
|
| 187 |
+
# qc.barrier()
|
| 188 |
+
|
| 189 |
+
return qc
|
| 190 |
+
|
| 191 |
+
def schro(nx, na, R, dt, initial_state, steps):
|
| 192 |
+
|
| 193 |
+
nq = int(np.ceil(np.log2(nx)))
|
| 194 |
+
|
| 195 |
+
# warped phase transformation
|
| 196 |
+
dp = 2 * R * np.pi / 2**na
|
| 197 |
+
p = np.arange(- R * np.pi, R * np.pi, step=dp)
|
| 198 |
+
fp = np.exp(-np.abs(p))
|
| 199 |
+
norm1 = np.linalg.norm(fp[2**(na-1):]) # norm of p>=0
|
| 200 |
+
|
| 201 |
+
# construct quantum circuit
|
| 202 |
+
system = QuantumRegister(2*nq+2, name='system')
|
| 203 |
+
ancilla = QuantumRegister(na, name='ancilla')
|
| 204 |
+
qc = QuantumCircuit(system, ancilla)
|
| 205 |
+
|
| 206 |
+
# initialization
|
| 207 |
+
prep = StatePreparation(initial_state)
|
| 208 |
+
anc_prep = StatePreparation(fp / np.linalg.norm(fp))
|
| 209 |
+
|
| 210 |
+
qc.append(prep, system)
|
| 211 |
+
# qc.append(anc_prep, ancilla)
|
| 212 |
+
qc.initialize(fp / np.linalg.norm(fp), ancilla)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
# QFT
|
| 216 |
+
qc.append(QFTGate(na), ancilla)
|
| 217 |
+
qc.x(ancilla[-1])
|
| 218 |
+
|
| 219 |
+
A1 = V1(nx, dt, name = "V1").to_gate()
|
| 220 |
+
A2 = V2(nx, dt, name = "V2")
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# Hamiltonian simulation for Nt steps
|
| 224 |
+
for i in range(steps):
|
| 225 |
+
# circuit for one step
|
| 226 |
+
for j in range(na):
|
| 227 |
+
# repeat controlled H1 for 2**j times
|
| 228 |
+
qc.append(A1.control().repeat(2**j), [ancilla[j]] + system[:])
|
| 229 |
+
|
| 230 |
+
# qc.append(A1.inverse().control(ctrl_state = "0").repeat(2**(na-1)), [ancilla[na-1]] + system[:])
|
| 231 |
+
qc.append(A1.inverse().repeat(2**(na-1)), system[:])
|
| 232 |
+
qc.append(A2, system[:])
|
| 233 |
+
|
| 234 |
+
# rearrange eta
|
| 235 |
+
qc.x(ancilla[-1])
|
| 236 |
+
qc.append(QFTGate(na).inverse(), ancilla)
|
| 237 |
+
|
| 238 |
+
return qc
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps):
|
| 243 |
+
|
| 244 |
+
qc = schro(nx, na, R, dt, initial_state, steps)
|
| 245 |
+
naimark = QuantumRegister(1, name='Naimark')
|
| 246 |
+
qc.add_register(naimark)
|
| 247 |
+
|
| 248 |
+
if field == 'Ez':
|
| 249 |
+
index = nx * y + x
|
| 250 |
+
elif field == 'Hx':
|
| 251 |
+
index = 2*nx*nx + nx * y + x
|
| 252 |
+
else:
|
| 253 |
+
index = 3*nx*nx + nx * y + x
|
| 254 |
+
|
| 255 |
+
index_bin = format(index, f'0{qc.num_qubits-2}b')
|
| 256 |
+
ctrl_state = '1' + index_bin
|
| 257 |
+
ctrl_qubits = qc.qubits[:-1]
|
| 258 |
+
qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state)
|
| 259 |
+
|
| 260 |
+
return qc
|
| 261 |
+
|
| 262 |
+
def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez'):
|
| 263 |
+
qc = schro(nx, na, R, dt, initial_state, steps)
|
| 264 |
+
|
| 265 |
+
naimark = QuantumRegister(1, name='Naimark')
|
| 266 |
+
qc.add_register(naimark)
|
| 267 |
+
|
| 268 |
+
if field == 'Ez':
|
| 269 |
+
index = nx * y + x
|
| 270 |
+
elif field == 'Hx':
|
| 271 |
+
index = 2*nx*nx + nx * y + x
|
| 272 |
+
else:
|
| 273 |
+
index = 3*nx*nx + nx * y + x
|
| 274 |
+
|
| 275 |
+
if field_ref == 'Ez':
|
| 276 |
+
index_ref = nx * yref + xref
|
| 277 |
+
elif field_ref == 'Hx':
|
| 278 |
+
index_ref = 2*nx*nx + nx * yref + xref
|
| 279 |
+
else:
|
| 280 |
+
index_ref = 3*nx*nx + nx * yref + xref
|
| 281 |
+
|
| 282 |
+
index_bin = [(index >> i) & 1 for i in range(qc.num_qubits-2)]
|
| 283 |
+
index_ref_bin = [(index_ref >> i) & 1 for i in range(qc.num_qubits-2)]
|
| 284 |
+
index_bin.append(1)
|
| 285 |
+
index_ref_bin.append(1)
|
| 286 |
+
|
| 287 |
+
#Convert reference bitstring to 00000
|
| 288 |
+
for i, bit in enumerate(index_ref_bin):
|
| 289 |
+
if bit == 1:
|
| 290 |
+
qc.x(i)
|
| 291 |
+
|
| 292 |
+
d_bits = [b ^ r for b, r in zip(index_ref_bin, index_bin)]
|
| 293 |
+
control = d_bits.index(1)
|
| 294 |
+
|
| 295 |
+
#Convert the other bitstring to 0001000
|
| 296 |
+
for target, bit in enumerate(d_bits):
|
| 297 |
+
if bit == 1 and target != control:
|
| 298 |
+
qc.cx(control, target)
|
| 299 |
+
qc.h(control)
|
| 300 |
+
|
| 301 |
+
ctrl_state_sum = '0'*(qc.num_qubits-1)
|
| 302 |
+
ctrl_state_diff = '0'*(qc.num_qubits-1-control-1)+'1'+'0'*(control)
|
| 303 |
+
|
| 304 |
+
qcdiff = qc.copy()
|
| 305 |
+
|
| 306 |
+
ctrl_qubits = qc.qubits[:-1]
|
| 307 |
+
|
| 308 |
+
qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_sum)
|
| 309 |
+
qcdiff.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_diff)
|
| 310 |
+
|
| 311 |
+
return qc, qcdiff
|
| 312 |
+
|
| 313 |
+
def get_absolute_field_value(qc, nq, na, offset, norm):
|
| 314 |
+
|
| 315 |
+
pauli_label = 'Z'+'I'*(2*nq+2+na)
|
| 316 |
+
observable = SparsePauliOp(Pauli(pauli_label))
|
| 317 |
+
########################################################################################
|
| 318 |
+
estimator = StatevectorEstimator()
|
| 319 |
+
|
| 320 |
+
# === Run Estimator (no parameters needed) ===
|
| 321 |
+
pub = (qc, observable)
|
| 322 |
+
job = estimator.run([pub])
|
| 323 |
+
result = job.result()[0]
|
| 324 |
+
z_exp = result.data.evs.item()
|
| 325 |
+
#########################################################################################
|
| 326 |
+
# === Compute projector expectation ===
|
| 327 |
+
pi_expect = (1 - z_exp) / 2
|
| 328 |
+
|
| 329 |
+
Absolute_value = norm*np.sqrt(pi_expect)-offset
|
| 330 |
+
|
| 331 |
+
return Absolute_value
|
| 332 |
+
|
| 333 |
+
def get_relative_sign(qc, qcdiff, nq, na):
|
| 334 |
+
|
| 335 |
+
pauli_label = 'Z'+'I'*(2*nq+2+na)
|
| 336 |
+
observable = SparsePauliOp(Pauli(pauli_label))
|
| 337 |
+
########################################################################################
|
| 338 |
+
estimator = StatevectorEstimator()
|
| 339 |
+
|
| 340 |
+
# === Run Estimator ===
|
| 341 |
+
pub = (qc, observable)
|
| 342 |
+
job = estimator.run([pub])
|
| 343 |
+
result = job.result()[0]
|
| 344 |
+
z_exp = result.data.evs.item()
|
| 345 |
+
|
| 346 |
+
pub_diff = (qcdiff, observable)
|
| 347 |
+
job_diff = estimator.run([pub_diff])
|
| 348 |
+
result_diff = job_diff.result()[0]
|
| 349 |
+
z_exp_diff = result_diff.data.evs.item()
|
| 350 |
+
#########################################################################################
|
| 351 |
+
# === Compute projector expectation ===
|
| 352 |
+
pi_expect_sum = (1 - z_exp) / 2
|
| 353 |
+
pi_expect_diff = (1 - z_exp_diff) / 2
|
| 354 |
+
|
| 355 |
+
relative_sign = 'same' if pi_expect_sum >= pi_expect_diff else 'different'
|
| 356 |
+
|
| 357 |
+
return relative_sign
|
| 358 |
+
|
| 359 |
+
def Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez'):
|
| 360 |
+
if steps < 31:
|
| 361 |
+
offset = 1
|
| 362 |
+
else :
|
| 363 |
+
offset = 0.15
|
| 364 |
+
deltastate = np.zeros(4*nx*nx)
|
| 365 |
+
# deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1
|
| 366 |
+
deltastate[nx*yref+xref] = 1
|
| 367 |
+
deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset
|
| 368 |
+
norm1 = np.linalg.norm(deltastate)
|
| 369 |
+
initial_state = deltastate/norm1
|
| 370 |
+
|
| 371 |
+
dp = 2 * R * np.pi / 2**na
|
| 372 |
+
p = np.arange(- R * np.pi, R * np.pi, step=dp)
|
| 373 |
+
fp = np.exp(-np.abs(p))
|
| 374 |
+
norm2 = np.linalg.norm(fp)
|
| 375 |
+
norm = norm1 * norm2
|
| 376 |
+
|
| 377 |
+
qc = circ_for_magnitude(field_ref, xref, yref, nx, na, R, dt, initial_state, steps)
|
| 378 |
+
|
| 379 |
+
Ezref = get_absolute_field_value(qc, nq, na, offset, norm)
|
| 380 |
+
|
| 381 |
+
return Ezref
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def transpile_circ(circ, basis_gates=None):
|
| 385 |
+
"""
|
| 386 |
+
Transpile the circuit to the specified basis gates.
|
| 387 |
+
"""
|
| 388 |
+
if basis_gates is None:
|
| 389 |
+
basis_gates = ['z', 'y', 'x', 'sdg', 's', 'h', 'rz', 'ry', 'rx', 'ecr', 'cz', 'cx']
|
| 390 |
+
|
| 391 |
+
transpiled_circ = transpile(circ, basis_gates=basis_gates)
|
| 392 |
+
return transpiled_circ
|
| 393 |
+
|
| 394 |
+
def compute_fidelity(circ1, circ2):
|
| 395 |
+
|
| 396 |
+
circ_1 = tensornetwork_from_circuit(transpile_circ(circ1), simulator_settings)
|
| 397 |
+
circ_2 = tensornetwork_from_circuit(transpile_circ(circ2), simulator_settings)
|
| 398 |
+
fidelity = abs(compute_overlap(circ_1, circ_2))**2
|
| 399 |
+
|
| 400 |
+
return fidelity
|
| 401 |
+
|
| 402 |
+
# def create_impulse_state(grid_dims, impulse_pos):
|
| 403 |
+
# """
|
| 404 |
+
# Creates an initial state vector with a single delta impulse at a specified grid position.
|
| 405 |
+
|
| 406 |
+
# The 2D grid is flattened into a 1D vector in row-major order, and this
|
| 407 |
+
# vector is then padded to match the full simulation state space size (4x).
|
| 408 |
+
|
| 409 |
+
# Args:
|
| 410 |
+
# grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
|
| 411 |
+
# For your original code, this would be (nx, nx).
|
| 412 |
+
# impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
|
| 413 |
+
# Coordinates are 0-indexed.
|
| 414 |
+
|
| 415 |
+
# Returns:
|
| 416 |
+
# numpy.ndarray: The full, padded initial state vector with a single 1.
|
| 417 |
+
|
| 418 |
+
# Raises:
|
| 419 |
+
# ValueError: If the impulse position is outside the grid dimensions.
|
| 420 |
+
# """
|
| 421 |
+
# grid_width, grid_height = grid_dims
|
| 422 |
+
# impulse_x, impulse_y = impulse_pos
|
| 423 |
+
|
| 424 |
+
# # --- Input Validation ---
|
| 425 |
+
# # Ensure the requested impulse position is actually on the grid.
|
| 426 |
+
# if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
|
| 427 |
+
# raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
|
| 428 |
+
# f"grid dimensions ({grid_width}x{grid_height}).")
|
| 429 |
+
|
| 430 |
+
# # --- 1. Calculate the 1D Array Index ---
|
| 431 |
+
# # Convert the (x, y) coordinate to a single index in a flattened 1D array.
|
| 432 |
+
# # The formula for row-major order is: index = y_coord * width + x_coord
|
| 433 |
+
# flat_index = impulse_y * grid_width + impulse_x
|
| 434 |
+
|
| 435 |
+
# # --- 2. Create the Full, Padded State Vector ---
|
| 436 |
+
# grid_size = grid_width * grid_height
|
| 437 |
+
# total_size = 4 * grid_size # The simulation space is 4x the grid size.
|
| 438 |
+
# initial_state = np.zeros(total_size)
|
| 439 |
+
|
| 440 |
+
# # --- 3. Set the Delta Impulse ---
|
| 441 |
+
# initial_state[flat_index] = 1
|
| 442 |
+
|
| 443 |
+
# return initial_state
|
utils/delta_impulse_generator.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import math
|
| 3 |
+
from qiskit.circuit import QuantumCircuit, QuantumRegister
|
| 4 |
+
from qiskit.circuit.library import StatePreparation, QFTGate, RZGate
|
| 5 |
+
from qiskit.quantum_info import Statevector
|
| 6 |
+
import pyvista as pv
|
| 7 |
+
|
| 8 |
+
def create_impulse_state(grid_dims, impulse_pos):
|
| 9 |
+
"""
|
| 10 |
+
Creates an initial state vector with a single delta impulse at a specified grid position.
|
| 11 |
+
|
| 12 |
+
The 2D grid is flattened into a 1D vector in row-major order, and this
|
| 13 |
+
vector is then padded to match the full simulation state space size (4x).
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
|
| 17 |
+
For your original code, this would be (nx, nx).
|
| 18 |
+
impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
|
| 19 |
+
Coordinates are 0-indexed.
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
numpy.ndarray: The full, padded initial state vector with a single 1.
|
| 23 |
+
|
| 24 |
+
Raises:
|
| 25 |
+
ValueError: If the impulse position is outside the grid dimensions.
|
| 26 |
+
"""
|
| 27 |
+
grid_width, grid_height = grid_dims
|
| 28 |
+
impulse_x, impulse_y = impulse_pos
|
| 29 |
+
|
| 30 |
+
# --- Input Validation ---
|
| 31 |
+
# Ensure the requested impulse position is actually on the grid.
|
| 32 |
+
if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
|
| 33 |
+
raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
|
| 34 |
+
f"grid dimensions ({grid_width}x{grid_height}).")
|
| 35 |
+
|
| 36 |
+
# --- 1. Calculate the 1D Array Index ---
|
| 37 |
+
# Convert the (x, y) coordinate to a single index in a flattened 1D array.
|
| 38 |
+
# The formula for row-major order is: index = y_coord * width + x_coord
|
| 39 |
+
flat_index = impulse_y * grid_width + impulse_x
|
| 40 |
+
|
| 41 |
+
# --- 2. Create the Full, Padded State Vector ---
|
| 42 |
+
grid_size = grid_width * grid_height
|
| 43 |
+
total_size = 4 * grid_size # The simulation space is 4x the grid size.
|
| 44 |
+
initial_state = np.zeros(total_size)
|
| 45 |
+
|
| 46 |
+
# --- 3. Set the Delta Impulse ---
|
| 47 |
+
initial_state[flat_index] = 1
|
| 48 |
+
|
| 49 |
+
return initial_state
|
| 50 |
+
|
| 51 |
+
def create_gaussian_state(grid_dims, mu, sigma):
|
| 52 |
+
"""
|
| 53 |
+
Creates an initial state vector with a 2D Gaussian distribution.
|
| 54 |
+
|
| 55 |
+
The state is normalized and padded to match the full simulation state space size (4x).
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
grid_dims (tuple): A tuple (width, height) defining the grid dimensions.
|
| 59 |
+
mu (tuple): A tuple (mu_x, mu_y) for the center (mean) of the Gaussian.
|
| 60 |
+
sigma (tuple): A tuple (sigma_x, sigma_y) for the standard deviation (spread).
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
numpy.ndarray: The full, padded initial state vector for the Gaussian state.
|
| 64 |
+
|
| 65 |
+
Raises:
|
| 66 |
+
ValueError: If sigma values are not positive.
|
| 67 |
+
"""
|
| 68 |
+
grid_width, grid_height = grid_dims
|
| 69 |
+
mu_x, mu_y = mu
|
| 70 |
+
sigma_x, sigma_y = sigma
|
| 71 |
+
|
| 72 |
+
if sigma_x <= 0 or sigma_y <= 0:
|
| 73 |
+
raise ValueError("Sigma values (spread) must be positive.")
|
| 74 |
+
|
| 75 |
+
# --- 1. Create a Coordinate Grid ---
|
| 76 |
+
x = np.arange(0, grid_width)
|
| 77 |
+
y = np.arange(0, grid_height)
|
| 78 |
+
X, Y = np.meshgrid(x, y)
|
| 79 |
+
|
| 80 |
+
# --- 2. Calculate the 2D Gaussian Function ---
|
| 81 |
+
gaussian_2d = np.exp(-((X - mu_x)**2 / (2 * sigma_x**2)) -
|
| 82 |
+
((Y - mu_y)**2 / (2 * sigma_y**2)))
|
| 83 |
+
|
| 84 |
+
# --- 3. Normalize the State Vector ---
|
| 85 |
+
# For a valid quantum state, the L2 norm (sum of squares of amplitudes) must be 1.
|
| 86 |
+
norm = np.linalg.norm(gaussian_2d)
|
| 87 |
+
if norm > 0:
|
| 88 |
+
gaussian_2d = gaussian_2d / norm
|
| 89 |
+
|
| 90 |
+
# --- 4. Flatten and Pad the Vector ---
|
| 91 |
+
gaussian_flat = gaussian_2d.flatten()
|
| 92 |
+
grid_size = grid_width * grid_height
|
| 93 |
+
total_size = 4 * grid_size
|
| 94 |
+
initial_state = np.pad(gaussian_flat, (0, total_size - grid_size), mode='constant')
|
| 95 |
+
|
| 96 |
+
return initial_state
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# --- New: Continuous-position helpers for excitation before meshing ---
|
| 103 |
+
def _normalize_to_unit(vec: np.ndarray) -> np.ndarray:
|
| 104 |
+
n = np.linalg.norm(vec)
|
| 105 |
+
return vec / n if n > 0 else vec
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def create_impulse_state_from_pos(grid_dims, pos01):
|
| 111 |
+
"""
|
| 112 |
+
Create a delta-like initial state from continuous position pos01=(x,y) in [0,1].
|
| 113 |
+
|
| 114 |
+
Why grid_dims?
|
| 115 |
+
- Simulation runs on a discrete nx×ny lattice; the continuous position must be
|
| 116 |
+
discretized onto that grid to produce the state vector fed into the solver.
|
| 117 |
+
- grid_dims provides (nx, ny) so we can map (x,y)∈[0,1]→grid coordinates via
|
| 118 |
+
gx = x*(nx-1), gy = y*(ny-1), then distribute amplitude bilinearly to the 4
|
| 119 |
+
neighboring nodes. This is required only for the simulation state, not the preview.
|
| 120 |
+
|
| 121 |
+
The preview uses create_impulse_preview_state(), which renders a smooth bump on a
|
| 122 |
+
fixed unit-square grid independent of nx for visualization.
|
| 123 |
+
"""
|
| 124 |
+
grid_width, grid_height = grid_dims
|
| 125 |
+
px, py = pos01
|
| 126 |
+
px = float(max(0.0, min(1.0, px)))
|
| 127 |
+
py = float(max(0.0, min(1.0, py)))
|
| 128 |
+
|
| 129 |
+
gx = px * (grid_width - 1)
|
| 130 |
+
gy = py * (grid_height - 1)
|
| 131 |
+
i0, j0 = int(np.floor(gx)), int(np.floor(gy))
|
| 132 |
+
i1, j1 = min(i0 + 1, grid_width - 1), min(j0 + 1, grid_height - 1)
|
| 133 |
+
dx, dy = gx - i0, gy - j0
|
| 134 |
+
|
| 135 |
+
w00 = (1 - dx) * (1 - dy)
|
| 136 |
+
w10 = dx * (1 - dy)
|
| 137 |
+
w01 = (1 - dx) * dy
|
| 138 |
+
w11 = dx * dy
|
| 139 |
+
|
| 140 |
+
grid_size = grid_width * grid_height
|
| 141 |
+
total_size = 4 * grid_size
|
| 142 |
+
field = np.zeros(grid_size)
|
| 143 |
+
field[j0 * grid_width + i0] += w00
|
| 144 |
+
field[j0 * grid_width + i1] += w10
|
| 145 |
+
field[j1 * grid_width + i0] += w01
|
| 146 |
+
field[j1 * grid_width + i1] += w11
|
| 147 |
+
field = _normalize_to_unit(field)
|
| 148 |
+
|
| 149 |
+
initial_state = np.zeros(total_size)
|
| 150 |
+
initial_state[:grid_size] = field
|
| 151 |
+
return initial_state
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def create_gaussian_state_from_pos(grid_dims, mu01, sigma01):
|
| 155 |
+
"""
|
| 156 |
+
Create a Gaussian initial state with center mu01=(x,y) and spreads sigma01=(sx,sy)
|
| 157 |
+
in [0,1] of the domain, then discretize to the solver grid given by grid_dims.
|
| 158 |
+
|
| 159 |
+
Why grid_dims?
|
| 160 |
+
- The quantum solver expects a vector aligned to the chosen nx×ny simulation grid.
|
| 161 |
+
We convert normalized μ and σ (fractions of the domain) into grid units using
|
| 162 |
+
(nx-1) and (ny-1). This step is necessary for the simulation, not for the preview.
|
| 163 |
+
|
| 164 |
+
For preview-only rendering, use create_impulse_preview_state() to keep the visuals
|
| 165 |
+
continuous and independent of nx.
|
| 166 |
+
"""
|
| 167 |
+
grid_width, grid_height = grid_dims
|
| 168 |
+
mu_x01, mu_y01 = mu01
|
| 169 |
+
sig_x01, sig_y01 = sigma01
|
| 170 |
+
|
| 171 |
+
mu_x01 = float(max(0.0, min(1.0, mu_x01)))
|
| 172 |
+
mu_y01 = float(max(0.0, min(1.0, mu_y01)))
|
| 173 |
+
sig_x01 = float(sig_x01)
|
| 174 |
+
sig_y01 = float(sig_y01)
|
| 175 |
+
if sig_x01 <= 0 or sig_y01 <= 0:
|
| 176 |
+
raise ValueError("Sigma values (spread) must be positive.")
|
| 177 |
+
|
| 178 |
+
mu_x = mu_x01 * (grid_width - 1)
|
| 179 |
+
mu_y = mu_y01 * (grid_height - 1)
|
| 180 |
+
sigma_x = sig_x01 * (grid_width - 1)
|
| 181 |
+
sigma_y = sig_y01 * (grid_height - 1)
|
| 182 |
+
|
| 183 |
+
x = np.arange(0, grid_width)
|
| 184 |
+
y = np.arange(0, grid_height)
|
| 185 |
+
X, Y = np.meshgrid(x, y)
|
| 186 |
+
gaussian_2d = np.exp(-((X - mu_x) ** 2) / (2 * sigma_x ** 2) - ((Y - mu_y) ** 2) / (2 * sigma_y ** 2))
|
| 187 |
+
|
| 188 |
+
field = _normalize_to_unit(gaussian_2d.ravel())
|
| 189 |
+
grid_size = grid_width * grid_height
|
| 190 |
+
total_size = 4 * grid_size
|
| 191 |
+
initial_state = np.zeros(total_size)
|
| 192 |
+
initial_state[:grid_size] = field
|
| 193 |
+
return initial_state
|
| 194 |
+
|
| 195 |
+
# --- Simulation Code (from previous context) ---
|
| 196 |
+
def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
|
| 197 |
+
qc = QuantumCircuit(n + j, name=name)
|
| 198 |
+
if j > 1: qc.cx(n + j - 1, range(n, n + j - 1))
|
| 199 |
+
if lam != 0: qc.p(lam, n + j - 1)
|
| 200 |
+
qc.h(n + j - 1)
|
| 201 |
+
if xgate and j > 1:
|
| 202 |
+
if isinstance(xgate, (list, tuple)):
|
| 203 |
+
for idx, flag in enumerate(xgate):
|
| 204 |
+
if flag: qc.x(n + idx)
|
| 205 |
+
elif xgate is True: qc.x(range(n, n + j - 1))
|
| 206 |
+
if j > 1:
|
| 207 |
+
mcrz = RZGate(theta).control(len(ctrl_state) + j - 1, ctrl_state="1" * (j - 1) + ctrl_state)
|
| 208 |
+
qc.append(mcrz, range(0, n + j))
|
| 209 |
+
else:
|
| 210 |
+
mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state=ctrl_state)
|
| 211 |
+
qc.append(mcrz, range(0, n + j))
|
| 212 |
+
if xgate and j > 1:
|
| 213 |
+
if isinstance(xgate, (list, tuple)):
|
| 214 |
+
for idx, flag in enumerate(xgate):
|
| 215 |
+
if flag: qc.x(n + idx)
|
| 216 |
+
elif xgate is True: qc.x(range(n, n + j - 1))
|
| 217 |
+
qc.h(n + j - 1)
|
| 218 |
+
if lam != 0: qc.p(-lam, n + j - 1)
|
| 219 |
+
if j > 1: qc.cx(n + j - 1, range(n, n + j - 1))
|
| 220 |
+
return qc.to_gate(label=name)
|
| 221 |
+
|
| 222 |
+
def V1(nx, dt):
|
| 223 |
+
n = int(np.ceil(np.log2(nx)))
|
| 224 |
+
derivatives, blocks = QuantumRegister(2 * n), QuantumRegister(2)
|
| 225 |
+
qc = QuantumCircuit(derivatives, blocks)
|
| 226 |
+
qc.append(Wj_block(2, n, "0" * n, -dt, 0, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
|
| 227 |
+
qc.append(Wj_block(3, n - 1, "1" * (n - 1), dt, 0, xgate=[0, 1]), list(derivatives[1:n]) + [derivatives[0]] + list(blocks[:]))
|
| 228 |
+
qc.append(Wj_block(1, n + 1, "0" * (n + 1), dt, 0, xgate=True), list(derivatives[n:2 * n]) + list(blocks[:]))
|
| 229 |
+
qc.append(Wj_block(2, n, "0" + "1" * (n - 1), -dt, 0, xgate=False), list(derivatives[n + 1:2 * n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
|
| 230 |
+
return qc
|
| 231 |
+
|
| 232 |
+
def V2(nx, dt):
|
| 233 |
+
n = int(np.ceil(np.log2(nx)))
|
| 234 |
+
derivatives, blocks = QuantumRegister(2 * n), QuantumRegister(2)
|
| 235 |
+
qc = QuantumCircuit(derivatives, blocks)
|
| 236 |
+
qc.append(Wj_block(2, 0, "", -2 * dt, -np.pi / 2, xgate=True), blocks[:])
|
| 237 |
+
for j in range(1, n + 1): qc.append(Wj_block(2 + j, 0, "", 2 * dt, -np.pi / 2, xgate=[1] * (j - 1) + [0, 1]), list(derivatives[0:j]) + list(blocks[:]))
|
| 238 |
+
qc.append(Wj_block(2, n, "0" * n, -dt, -np.pi / 2, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
|
| 239 |
+
qc.append(Wj_block(2, n, "1" * n, 2 * dt, -np.pi / 2, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
|
| 240 |
+
qc.append(Wj_block(3, n - 1, "1" * (n - 1), dt, -np.pi / 2, xgate=[0, 1]), list(derivatives[1:n]) + [derivatives[0]] + list(blocks[:]))
|
| 241 |
+
qc.append(Wj_block(1, 1, "0", 2 * dt, -np.pi / 2, xgate=False), blocks[:])
|
| 242 |
+
for j in range(1, n + 1): qc.append(Wj_block(1 + j, 1, "0", -2 * dt, -np.pi / 2, xgate=[1] * (j - 1)), [blocks[0]] + list(derivatives[n:n + j]) + [blocks[1]])
|
| 243 |
+
qc.append(Wj_block(1, n + 1, "0" * (n + 1), dt, -np.pi / 2, xgate=False), list(derivatives[n:2 * n]) + list(blocks[:]))
|
| 244 |
+
qc.append(Wj_block(1, n + 1, "0" + "1" * n, -2 * dt, -np.pi / 2, xgate=False), list(derivatives[n:2 * n]) + list(blocks[:]))
|
| 245 |
+
qc.append(Wj_block(2, n, "0" + "1" * (n - 1), -dt, -np.pi / 2, xgate=False), list(derivatives[n + 1:2 * n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
|
| 246 |
+
return qc
|
| 247 |
+
|
| 248 |
+
def run_sim(nx, na, R, initial_state, T, snapshot_dt=None, stop_check=None, progress_callback=None):
|
| 249 |
+
"""
|
| 250 |
+
Runs the quantum simulation for electromagnetic scattering with fixed dt=0.1.
|
| 251 |
+
Captures frames only at user-defined snapshot times: [0, Δt, 2Δt, ..., ≤ T_eff],
|
| 252 |
+
always including t=0 and the final solver-aligned T (T_eff = floor(T/dt)*dt).
|
| 253 |
+
|
| 254 |
+
Returns:
|
| 255 |
+
frames (np.ndarray), snapshot_times (np.ndarray)
|
| 256 |
+
"""
|
| 257 |
+
dt = 0.1
|
| 258 |
+
# Validate total time and compute solver-aligned end time
|
| 259 |
+
try:
|
| 260 |
+
T_val = float(T)
|
| 261 |
+
except Exception:
|
| 262 |
+
return np.array([]), np.array([])
|
| 263 |
+
if T_val <= 0:
|
| 264 |
+
return np.array([]), np.array([])
|
| 265 |
+
|
| 266 |
+
steps = int(np.floor(T_val / dt))
|
| 267 |
+
if steps <= 0:
|
| 268 |
+
return np.array([]), np.array([])
|
| 269 |
+
T_eff = steps * dt
|
| 270 |
+
|
| 271 |
+
# Determine snapshot Δt on solver grid
|
| 272 |
+
tol = 1e-12
|
| 273 |
+
if snapshot_dt is None:
|
| 274 |
+
snapshot_dt_val = dt
|
| 275 |
+
else:
|
| 276 |
+
try:
|
| 277 |
+
snapshot_dt_val = float(snapshot_dt)
|
| 278 |
+
except Exception:
|
| 279 |
+
snapshot_dt_val = dt
|
| 280 |
+
if snapshot_dt_val < dt - tol:
|
| 281 |
+
snapshot_dt_val = dt
|
| 282 |
+
k = max(1, int(round(snapshot_dt_val / dt)))
|
| 283 |
+
snapshot_dt_eff = k * dt
|
| 284 |
+
|
| 285 |
+
# Build requested snapshot times on solver grid
|
| 286 |
+
target_times = [0.0]
|
| 287 |
+
t = 0.0
|
| 288 |
+
while t + snapshot_dt_eff <= T_eff + tol:
|
| 289 |
+
t = round(t + snapshot_dt_eff, 12)
|
| 290 |
+
if t <= T_eff + tol:
|
| 291 |
+
target_times.append(min(t, T_eff))
|
| 292 |
+
if abs(target_times[-1] - T_eff) > tol:
|
| 293 |
+
target_times.append(T_eff)
|
| 294 |
+
|
| 295 |
+
# Setup circuit
|
| 296 |
+
nq = int(np.ceil(np.log2(nx)))
|
| 297 |
+
dp = 2 * R * np.pi / 2 ** na
|
| 298 |
+
p = np.arange(-R * np.pi, R * np.pi, step=dp)
|
| 299 |
+
fp = np.exp(-np.abs(p))
|
| 300 |
+
system, ancilla = QuantumRegister(2 * nq + 2), QuantumRegister(na)
|
| 301 |
+
qc = QuantumCircuit(system, ancilla)
|
| 302 |
+
qc.append(StatePreparation(initial_state), system)
|
| 303 |
+
qc.append(StatePreparation(fp / np.linalg.norm(fp)), ancilla)
|
| 304 |
+
expA1 = V1(nx, dt).to_gate()
|
| 305 |
+
expA2 = V2(nx, dt)
|
| 306 |
+
|
| 307 |
+
frames = []
|
| 308 |
+
# Capture initial frame at t=0
|
| 309 |
+
sv0 = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))
|
| 310 |
+
frames.append(sv0[2 ** (na - 1)])
|
| 311 |
+
next_idx = 1 # next target_times index to capture
|
| 312 |
+
|
| 313 |
+
for i in range(steps):
|
| 314 |
+
if stop_check and stop_check():
|
| 315 |
+
print(f"Simulation interrupted at step {i}/{steps}")
|
| 316 |
+
break
|
| 317 |
+
# One solver step
|
| 318 |
+
qc.append(QFTGate(na), ancilla)
|
| 319 |
+
qc.x(ancilla[-1])
|
| 320 |
+
for j in range(na - 1):
|
| 321 |
+
qc.append(expA1.control().repeat(2 ** j), [ancilla[j]] + system[:])
|
| 322 |
+
qc.append(expA1.inverse().control(ctrl_state="0").repeat(2 ** (na - 1)), [ancilla[na - 1]] + system[:])
|
| 323 |
+
qc.append(expA2, system[:])
|
| 324 |
+
qc.x(ancilla[-1])
|
| 325 |
+
qc.append(QFTGate(na).inverse(), ancilla)
|
| 326 |
+
|
| 327 |
+
current_time = (i + 1) * dt
|
| 328 |
+
if next_idx < len(target_times) and abs(current_time - target_times[next_idx]) <= tol:
|
| 329 |
+
u = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))
|
| 330 |
+
frames.append(u[2 ** (na - 1)])
|
| 331 |
+
next_idx += 1
|
| 332 |
+
|
| 333 |
+
if progress_callback:
|
| 334 |
+
try:
|
| 335 |
+
progress = ((i + 1) / steps) * 100
|
| 336 |
+
progress_callback(progress)
|
| 337 |
+
except Exception:
|
| 338 |
+
pass
|
| 339 |
+
|
| 340 |
+
if progress_callback:
|
| 341 |
+
try:
|
| 342 |
+
progress_callback(100.0)
|
| 343 |
+
except Exception:
|
| 344 |
+
pass
|
| 345 |
+
|
| 346 |
+
# Ensure snapshot_times align with number of captured frames (covers early stop)
|
| 347 |
+
frames_arr = np.asarray(frames)
|
| 348 |
+
times_arr = np.asarray(target_times[: len(frames_arr)])
|
| 349 |
+
return frames_arr, times_arr
|
| 350 |
+
|
| 351 |
+
def create_impulse_preview_state(preview_n: int, pos01, sigma01: float = 0.02):
|
| 352 |
+
"""
|
| 353 |
+
Smooth delta-like preview on a unit square using a narrow Gaussian (sigma in [0,1]).
|
| 354 |
+
Preview-only helper, independent of simulation grid size (nx). Use this for the
|
| 355 |
+
Excitation preview; use the *_from_pos() variants for the actual simulation.
|
| 356 |
+
"""
|
| 357 |
+
try:
|
| 358 |
+
sx = float(sigma01) if sigma01 and sigma01 > 0 else 0.02
|
| 359 |
+
except Exception:
|
| 360 |
+
sx = 0.02
|
| 361 |
+
return create_gaussian_state_from_pos((int(preview_n), int(preview_n)), (float(pos01[0]), float(pos01[1])), (sx, sx))
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
##### IBM QPU Simulation Code Below (Do Not Edit) #####
|
| 369 |
+
|
| 370 |
+
from .base_functions import *
|
| 371 |
+
|
| 372 |
+
def create_time_frames(total_time, snapshot_interval):
|
| 373 |
+
dt = 0.1
|
| 374 |
+
tol = 1e-9
|
| 375 |
+
try:
|
| 376 |
+
T_val = float(total_time)
|
| 377 |
+
except (ValueError, TypeError):
|
| 378 |
+
return []
|
| 379 |
+
if T_val <= 0:
|
| 380 |
+
return []
|
| 381 |
+
steps = int(np.floor(T_val / dt))
|
| 382 |
+
if steps <= 0:
|
| 383 |
+
return [0.0]
|
| 384 |
+
T_eff = steps * dt
|
| 385 |
+
try:
|
| 386 |
+
snapshot_dt_val = float(snapshot_interval)
|
| 387 |
+
except (ValueError, TypeError):
|
| 388 |
+
snapshot_dt_val = dt
|
| 389 |
+
if snapshot_dt_val < dt:
|
| 390 |
+
snapshot_dt_val = dt
|
| 391 |
+
k = max(1, int(round(snapshot_dt_val / dt)))
|
| 392 |
+
snapshot_dt_eff = k * dt
|
| 393 |
+
times = np.arange(0, T_eff + tol, snapshot_dt_eff)
|
| 394 |
+
if abs(times[-1] - T_eff) > tol:
|
| 395 |
+
times = np.append(times, T_eff)
|
| 396 |
+
times = np.round(times, 12)
|
| 397 |
+
unique_times = []
|
| 398 |
+
for t in times:
|
| 399 |
+
if not unique_times or abs(t - unique_times[-1]) > tol:
|
| 400 |
+
unique_times.append(float(t))
|
| 401 |
+
return unique_times
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def run_qpu(field, x, y, T, snapshot_time, nx, initial_state, impulse_pos):
|
| 406 |
+
"""
|
| 407 |
+
Compute time-series field values. Supports single-point and multi-point modes.
|
| 408 |
+
|
| 409 |
+
- Single-point (backward compatible): x, y are integers; returns list[float].
|
| 410 |
+
- Multi-point: x is a list/tuple of (ix, iy) integer pairs and y is None; returns dict[(ix,iy) -> list[float]].
|
| 411 |
+
"""
|
| 412 |
+
na = 1
|
| 413 |
+
dt = 0.1
|
| 414 |
+
R = 4
|
| 415 |
+
nq = int(np.ceil(np.log2(nx)))
|
| 416 |
+
|
| 417 |
+
# Normalize monitor points input
|
| 418 |
+
if isinstance(x, (list, tuple)) and y is None:
|
| 419 |
+
points = [tuple(map(int, pt)) for pt in x]
|
| 420 |
+
multi = True
|
| 421 |
+
else:
|
| 422 |
+
points = [(int(x), int(y))]
|
| 423 |
+
multi = False
|
| 424 |
+
|
| 425 |
+
xref, yref = impulse_pos
|
| 426 |
+
|
| 427 |
+
offset = 0
|
| 428 |
+
grid_dims = (nx, nx)
|
| 429 |
+
initial_state = create_impulse_state(grid_dims, impulse_pos)
|
| 430 |
+
|
| 431 |
+
dp = 2 * R * np.pi / 2**na
|
| 432 |
+
p = np.arange(- R * np.pi, R * np.pi, step=dp)
|
| 433 |
+
fp = np.exp(-np.abs(p))
|
| 434 |
+
norm = np.linalg.norm(fp)
|
| 435 |
+
|
| 436 |
+
time_frames = create_time_frames(T, snapshot_time)
|
| 437 |
+
|
| 438 |
+
# Prepare outputs
|
| 439 |
+
if multi:
|
| 440 |
+
series_by_point = { (px, py): [] for (px, py) in points }
|
| 441 |
+
else:
|
| 442 |
+
series_single = []
|
| 443 |
+
|
| 444 |
+
for time in time_frames:
|
| 445 |
+
steps = int(math.ceil(time / dt))
|
| 446 |
+
# Reference Ez field at impulse location for sign
|
| 447 |
+
Eref = Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref='Ez')
|
| 448 |
+
|
| 449 |
+
for (px, py) in points:
|
| 450 |
+
circ_magnitude = circ_for_magnitude(field, px, py, nx, na, R, dt, initial_state, steps)
|
| 451 |
+
magnitude = get_absolute_field_value(circ_magnitude, nq, na, offset, norm)
|
| 452 |
+
|
| 453 |
+
if field == 'Ez' and px == xref and py == yref:
|
| 454 |
+
Field_value = -magnitude if Eref < 0 else magnitude
|
| 455 |
+
else:
|
| 456 |
+
circsum, circdiff = circuits_for_sign(field, px, py, nx, na, dt, R, initial_state, steps, xref, yref, field_ref='Ez')
|
| 457 |
+
sign = get_relative_sign(circsum, circdiff, nq, na)
|
| 458 |
+
if (sign == 'same' and Eref > 0) or (sign == 'different' and Eref < 0):
|
| 459 |
+
Field_value = magnitude
|
| 460 |
+
else:
|
| 461 |
+
Field_value = -magnitude
|
| 462 |
+
|
| 463 |
+
if multi:
|
| 464 |
+
series_by_point[(px, py)].append(Field_value)
|
| 465 |
+
else:
|
| 466 |
+
series_single.append(Field_value)
|
| 467 |
+
|
| 468 |
+
return series_by_point if multi else series_single
|
utils/fdtd_demo.ipynb
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 2,
|
| 6 |
+
"id": "c3fb397b",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import numpy as np\n",
|
| 11 |
+
"import scipy.sparse as sp\n",
|
| 12 |
+
"import math\n",
|
| 13 |
+
"import matplotlib.pyplot as plt\n",
|
| 14 |
+
"from scipy.special import jn\n",
|
| 15 |
+
"from scipy.sparse import identity, csr_matrix, kron, diags, eye\n",
|
| 16 |
+
"from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister\n",
|
| 17 |
+
"from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate\n",
|
| 18 |
+
"from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli\n",
|
| 19 |
+
"from scipy.linalg import expm\n",
|
| 20 |
+
"# from tools import *\n",
|
| 21 |
+
"from base_functions import *\n",
|
| 22 |
+
"from qiskit.qasm3 import dumps # QASM 3 exporter\n",
|
| 23 |
+
"from qiskit.qasm3 import loads\n",
|
| 24 |
+
"from qiskit.circuit.library import QFT\n",
|
| 25 |
+
"from qiskit.primitives import StatevectorEstimator\n",
|
| 26 |
+
"from qiskit import transpile"
|
| 27 |
+
]
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"cell_type": "code",
|
| 31 |
+
"execution_count": null,
|
| 32 |
+
"id": "9c5ad55d",
|
| 33 |
+
"metadata": {},
|
| 34 |
+
"outputs": [],
|
| 35 |
+
"source": [
|
| 36 |
+
"# Inputs\n",
|
| 37 |
+
"nx = 16\n",
|
| 38 |
+
"grid_dims=(nx,nx)\n",
|
| 39 |
+
"field = 'Hx' \n",
|
| 40 |
+
"x = 7\n",
|
| 41 |
+
"y = 7\n",
|
| 42 |
+
"T = 3\n",
|
| 43 |
+
"initial_state = 'delta'\n",
|
| 44 |
+
"impulse_pos = (nx//2,nx//2)"
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"cell_type": "markdown",
|
| 49 |
+
"id": "ddb3e66c",
|
| 50 |
+
"metadata": {},
|
| 51 |
+
"source": [
|
| 52 |
+
"# Get field at any point"
|
| 53 |
+
]
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"cell_type": "code",
|
| 57 |
+
"execution_count": null,
|
| 58 |
+
"id": "f885e0a1",
|
| 59 |
+
"metadata": {},
|
| 60 |
+
"outputs": [
|
| 61 |
+
{
|
| 62 |
+
"name": "stdout",
|
| 63 |
+
"output_type": "stream",
|
| 64 |
+
"text": [
|
| 65 |
+
"Time step 0: Ez=2.2376563277501641e-07, Hy=2.2001300842051478e-07, Hx=2.2101993820490713e-07\n",
|
| 66 |
+
"Time step 1: Ez=0.03713901686823572, Hy=0.07449276224003892, Hx=-0.09915963436446357\n",
|
| 67 |
+
"Time step 2: Ez=0.1637729220450236, Hy=0.057764577526220356, Hx=-0.06137018919000033\n"
|
| 68 |
+
]
|
| 69 |
+
}
|
| 70 |
+
],
|
| 71 |
+
"source": [
|
| 72 |
+
"def get_field_value(field, x, y, T, nx, initial_state, impulse_pos):\n",
|
| 73 |
+
" na = 1\n",
|
| 74 |
+
" dt = 0.1\n",
|
| 75 |
+
" R = 4\n",
|
| 76 |
+
" nq = int(np.ceil(np.log2(nx)))\n",
|
| 77 |
+
" \n",
|
| 78 |
+
" xref, yref = impulse_pos\n",
|
| 79 |
+
"\n",
|
| 80 |
+
" offset = 0\n",
|
| 81 |
+
" # deltastate = np.zeros(4*nx*nx)\n",
|
| 82 |
+
" # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1\n",
|
| 83 |
+
" # deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset\n",
|
| 84 |
+
" # norm1 = np.linalg.norm(deltastate)\n",
|
| 85 |
+
" # initial_state = deltastate/norm1\n",
|
| 86 |
+
" grid_dims = (nx,nx)\n",
|
| 87 |
+
" initial_state = create_impulse_state(grid_dims, impulse_pos)\n",
|
| 88 |
+
"\n",
|
| 89 |
+
" dp = 2 * R * np.pi / 2**na\n",
|
| 90 |
+
" p = np.arange(- R * np.pi, R * np.pi, step=dp)\n",
|
| 91 |
+
" fp = np.exp(-np.abs(p))\n",
|
| 92 |
+
" norm = np.linalg.norm(fp)\n",
|
| 93 |
+
"\n",
|
| 94 |
+
" steps = int(math.ceil(T / dt))\n",
|
| 95 |
+
"\n",
|
| 96 |
+
" Eref = Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez')\n",
|
| 97 |
+
" \n",
|
| 98 |
+
" circ_magnitude = circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps)\n",
|
| 99 |
+
" magnitude = get_absolute_field_value(circ_magnitude, nq, na, offset, norm)\n",
|
| 100 |
+
"\n",
|
| 101 |
+
" if field=='Ez' and x == xref and y == yref:\n",
|
| 102 |
+
" if Eref< 0:\n",
|
| 103 |
+
" Field_value = -1*magnitude\n",
|
| 104 |
+
" else:\n",
|
| 105 |
+
" Field_value = magnitude\n",
|
| 106 |
+
" else: \n",
|
| 107 |
+
" circsum, circdiff = circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez')\n",
|
| 108 |
+
" sign = get_relative_sign(circsum, circdiff, nq, na)\n",
|
| 109 |
+
"\n",
|
| 110 |
+
" if (sign == 'same' and Eref > 0) or (sign == 'different' and Eref < 0):\n",
|
| 111 |
+
"\n",
|
| 112 |
+
" Field_value = magnitude\n",
|
| 113 |
+
" else:\n",
|
| 114 |
+
" Field_value = -1*magnitude\n",
|
| 115 |
+
"\n",
|
| 116 |
+
" return Field_value\n",
|
| 117 |
+
"\n",
|
| 118 |
+
"for i in range(T):\n",
|
| 119 |
+
" Exz = get_field_value('Ez', x, y, i, nx, initial_state, impulse_pos)\n",
|
| 120 |
+
" Hy = get_field_value('Hy', x, y, i, nx, initial_state, impulse_pos)\n",
|
| 121 |
+
" Hx = get_field_value('Hx', x, y, i, nx, initial_state, impulse_pos)\n",
|
| 122 |
+
" print(f'Time step {i}: Ez={Exz}, Hy={Hy}, Hx={Hx}')\n",
|
| 123 |
+
"\n",
|
| 124 |
+
"\n",
|
| 125 |
+
"# pl"
|
| 126 |
+
]
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"cell_type": "code",
|
| 130 |
+
"execution_count": null,
|
| 131 |
+
"id": "1c7af009",
|
| 132 |
+
"metadata": {},
|
| 133 |
+
"outputs": [],
|
| 134 |
+
"source": [
|
| 135 |
+
"import numpy as np\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"def create_time_frames(total_time, snapshot_interval):\n",
|
| 138 |
+
" \"\"\"\n",
|
| 139 |
+
" Generates a list of snapshot times aligned with the quantum solver's fixed timestep.\n",
|
| 140 |
+
"\n",
|
| 141 |
+
" The solver uses a fixed internal timestep (dt=0.1). This function calculates\n",
|
| 142 |
+
" the effective total time and snapshot interval that align with this grid,\n",
|
| 143 |
+
" replicating the logic from the `run_sim` function.\n",
|
| 144 |
+
"\n",
|
| 145 |
+
" Args:\n",
|
| 146 |
+
" total_time (float): The desired total simulation time (T).\n",
|
| 147 |
+
" snapshot_interval (float): The desired time between snapshots (Δt).\n",
|
| 148 |
+
"\n",
|
| 149 |
+
" Returns:\n",
|
| 150 |
+
" list: A list of floats representing the actual snapshot time frames.\n",
|
| 151 |
+
" \"\"\"\n",
|
| 152 |
+
" # Solver's fixed internal timestep\n",
|
| 153 |
+
" dt = 0.1\n",
|
| 154 |
+
" tol = 1e-9\n",
|
| 155 |
+
"\n",
|
| 156 |
+
" # Validate inputs and compute the solver-aligned end time (T_eff)\n",
|
| 157 |
+
" try:\n",
|
| 158 |
+
" T_val = float(total_time)\n",
|
| 159 |
+
" except (ValueError, TypeError):\n",
|
| 160 |
+
" return []\n",
|
| 161 |
+
" if T_val <= 0:\n",
|
| 162 |
+
" return []\n",
|
| 163 |
+
"\n",
|
| 164 |
+
" steps = int(np.floor(T_val / dt))\n",
|
| 165 |
+
" if steps <= 0:\n",
|
| 166 |
+
" return [0.0] # Only the initial frame at t=0 exists\n",
|
| 167 |
+
" T_eff = steps * dt\n",
|
| 168 |
+
"\n",
|
| 169 |
+
" # Determine the effective snapshot interval on the solver grid (snapshot_dt_eff)\n",
|
| 170 |
+
" try:\n",
|
| 171 |
+
" snapshot_dt_val = float(snapshot_interval)\n",
|
| 172 |
+
" except (ValueError, TypeError):\n",
|
| 173 |
+
" snapshot_dt_val = dt # Default to solver dt if invalid\n",
|
| 174 |
+
"\n",
|
| 175 |
+
" if snapshot_dt_val < dt - tol:\n",
|
| 176 |
+
" snapshot_dt_val = dt # Clamp to the minimum possible interval\n",
|
| 177 |
+
" \n",
|
| 178 |
+
" # Snap the interval to the nearest multiple of the solver's dt\n",
|
| 179 |
+
" k = max(1, int(round(snapshot_dt_val / dt)))\n",
|
| 180 |
+
" snapshot_dt_eff = k * dt\n",
|
| 181 |
+
"\n",
|
| 182 |
+
" # Build the list of target times using the same logic as run_sim\n",
|
| 183 |
+
" target_times = [0.0]\n",
|
| 184 |
+
" current_time = 0.0\n",
|
| 185 |
+
" while current_time + snapshot_dt_eff <= T_eff + tol:\n",
|
| 186 |
+
" current_time = round(current_time + snapshot_dt_eff, 12)\n",
|
| 187 |
+
" target_times.append(min(current_time, T_eff))\n",
|
| 188 |
+
" \n",
|
| 189 |
+
" # Ensure the effective end time is included if the loop steps over it\n",
|
| 190 |
+
" if abs(target_times[-1] - T_eff) > tol:\n",
|
| 191 |
+
" target_times.append(T_eff)\n",
|
| 192 |
+
" \n",
|
| 193 |
+
" # Remove duplicates that might arise from floating point issues\n",
|
| 194 |
+
" unique_times = []\n",
|
| 195 |
+
" for t in target_times:\n",
|
| 196 |
+
" if not unique_times or abs(t - unique_times[-1]) > tol:\n",
|
| 197 |
+
" unique_times.append(t)\n",
|
| 198 |
+
"\n",
|
| 199 |
+
" return unique_times\n",
|
| 200 |
+
"\n",
|
| 201 |
+
"# --- Example ---\n",
|
| 202 |
+
"T = 1.0\n",
|
| 203 |
+
"# Let's use an interval that is not a multiple of 0.1\n",
|
| 204 |
+
"snapshot_time = 0.3\n",
|
| 205 |
+
"# The function will snap 0.33 to the nearest multiple of 0.1, which is 0.3 (k=3)\n",
|
| 206 |
+
"\n",
|
| 207 |
+
"time_frames = create_time_frames(T, snapshot_time)\n",
|
| 208 |
+
"\n",
|
| 209 |
+
"print(f\"Total Time: {T}\")\n",
|
| 210 |
+
"print(f\"Requested Snapshot Interval: {snapshot_time}\")\n",
|
| 211 |
+
"print(f\"Generated Time Frames: {time_frames}\")\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"\n",
|
| 214 |
+
"\n",
|
| 215 |
+
"\n"
|
| 216 |
+
]
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"cell_type": "code",
|
| 220 |
+
"execution_count": null,
|
| 221 |
+
"id": "719cbaf4",
|
| 222 |
+
"metadata": {},
|
| 223 |
+
"outputs": [],
|
| 224 |
+
"source": [
|
| 225 |
+
"def run_qpu(field, x, y, T, snapshot_time, nx, initial_state, impulse_pos):\n",
|
| 226 |
+
" na = 1\n",
|
| 227 |
+
" dt = 0.1\n",
|
| 228 |
+
" R = 4\n",
|
| 229 |
+
" nq = int(np.ceil(np.log2(nx)))\n",
|
| 230 |
+
" \n",
|
| 231 |
+
" xref, yref = impulse_pos\n",
|
| 232 |
+
"\n",
|
| 233 |
+
" offset = 0\n",
|
| 234 |
+
" # deltastate = np.zeros(4*nx*nx)\n",
|
| 235 |
+
" # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1\n",
|
| 236 |
+
" # deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset\n",
|
| 237 |
+
" # norm1 = np.linalg.norm(deltastate)\n",
|
| 238 |
+
" # initial_state = deltastate/norm1\n",
|
| 239 |
+
" grid_dims = (nx,nx)\n",
|
| 240 |
+
" initial_state = create_impulse_state(grid_dims, impulse_pos)\n",
|
| 241 |
+
" dp = 2 * R * np.pi / 2**na\n",
|
| 242 |
+
" p = np.arange(- R * np.pi, R * np.pi, step=dp)\n",
|
| 243 |
+
" fp = np.exp(-np.abs(p))\n",
|
| 244 |
+
" norm = np.linalg.norm(fp)\n",
|
| 245 |
+
"\n",
|
| 246 |
+
" time_frames = create_time_frames(T, snapshot_time)\n",
|
| 247 |
+
" field_values = []\n",
|
| 248 |
+
" for time in time_frames:\n",
|
| 249 |
+
" \n",
|
| 250 |
+
" steps = int(math.ceil(time / dt))\n",
|
| 251 |
+
" # use \n",
|
| 252 |
+
"\n",
|
| 253 |
+
" Eref = Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez')\n",
|
| 254 |
+
"\n",
|
| 255 |
+
" circ_magnitude = circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps)\n",
|
| 256 |
+
" magnitude = get_absolute_field_value(circ_magnitude, nq, na, offset, norm)\n",
|
| 257 |
+
"\n",
|
| 258 |
+
" if field=='Ez' and x == xref and y == yref:\n",
|
| 259 |
+
" if Eref< 0:\n",
|
| 260 |
+
" Field_value = -1*magnitude\n",
|
| 261 |
+
" else:\n",
|
| 262 |
+
" Field_value = magnitude\n",
|
| 263 |
+
" else: \n",
|
| 264 |
+
" circsum, circdiff = circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez')\n",
|
| 265 |
+
" sign = get_relative_sign(circsum, circdiff, nq, na)\n",
|
| 266 |
+
"\n",
|
| 267 |
+
" if (sign == 'same' and Eref > 0) or (sign == 'different' and Eref < 0):\n",
|
| 268 |
+
"\n",
|
| 269 |
+
" Field_value = magnitude\n",
|
| 270 |
+
" else:\n",
|
| 271 |
+
" Field_value = -1*magnitude\n",
|
| 272 |
+
" field_values.append(Field_value)\n",
|
| 273 |
+
" return field_values\n",
|
| 274 |
+
"\n",
|
| 275 |
+
"\n",
|
| 276 |
+
"Exz = get_field_value('Ez', x, y, T, 0.3, nx, initial_state, impulse_pos)\n",
|
| 277 |
+
"Hy = get_field_value('Hy', x, y, T, 0.3, nx, initial_state, impulse_pos)\n",
|
| 278 |
+
"Hx = get_field_value('Hx', x, y, T, 0.3, nx, initial_state, impulse_pos)\n",
|
| 279 |
+
"# pl"
|
| 280 |
+
]
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"cell_type": "code",
|
| 284 |
+
"execution_count": 27,
|
| 285 |
+
"id": "76ded7b1",
|
| 286 |
+
"metadata": {},
|
| 287 |
+
"outputs": [
|
| 288 |
+
{
|
| 289 |
+
"name": "stdout",
|
| 290 |
+
"output_type": "stream",
|
| 291 |
+
"text": [
|
| 292 |
+
"[np.float64(2.2376563277501641e-07), np.float64(9.736590876164155e-05), np.float64(0.0044225353473238554), np.float64(0.024957214558959943), np.float64(0.03713901686823572)]\n",
|
| 293 |
+
"[np.float64(2.2001300842051478e-07), np.float64(0.0009704034518815665), np.float64(0.01717008973114835), np.float64(0.05764097025932368), np.float64(0.07449276224003892)]\n",
|
| 294 |
+
"[np.float64(2.2101993820490713e-07), np.float64(-0.0038329627633077764), np.float64(-0.029433776288711664), np.float64(-0.07996477735815476), np.float64(-0.09915963436446357)]\n"
|
| 295 |
+
]
|
| 296 |
+
}
|
| 297 |
+
],
|
| 298 |
+
"source": [
|
| 299 |
+
"print(Exz)\n",
|
| 300 |
+
"print(Hy)\n",
|
| 301 |
+
"print(Hx)"
|
| 302 |
+
]
|
| 303 |
+
}
|
| 304 |
+
],
|
| 305 |
+
"metadata": {
|
| 306 |
+
"kernelspec": {
|
| 307 |
+
"display_name": "Python 3",
|
| 308 |
+
"language": "python",
|
| 309 |
+
"name": "python3"
|
| 310 |
+
},
|
| 311 |
+
"language_info": {
|
| 312 |
+
"codemirror_mode": {
|
| 313 |
+
"name": "ipython",
|
| 314 |
+
"version": 3
|
| 315 |
+
},
|
| 316 |
+
"file_extension": ".py",
|
| 317 |
+
"mimetype": "text/x-python",
|
| 318 |
+
"name": "python",
|
| 319 |
+
"nbconvert_exporter": "python",
|
| 320 |
+
"pygments_lexer": "ipython3",
|
| 321 |
+
"version": "3.11.9"
|
| 322 |
+
}
|
| 323 |
+
},
|
| 324 |
+
"nbformat": 4,
|
| 325 |
+
"nbformat_minor": 5
|
| 326 |
+
}
|
utils/tempCodeRunnerFile.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from tools import *
|