quantum / em /ui.py
harishaseebat92
EM : IBM/ION Job Retrieve Upload Window
6ca7a4a
"""UI components and build_ui function for the EM module."""
from __future__ import annotations
from typing import TYPE_CHECKING
import plotly.graph_objects as go
from trame.widgets import html as trame_html, vuetify3, plotly as plotly_widgets
from pyvista.trame.ui import plotter_ui
from .state import state, ctrl, get_server
from .globals import plotter, GRID_SIZES
from .geometry import build_geometry_placeholder as _build_geometry_placeholder
from .excitation import build_excitation_placeholder as _build_excitation_placeholder
from .simulation import run_simulation_only, reset_to_defaults, stop_simulation_handler, process_uploaded_em_job_result
from .exports import (
export_vtk, export_vtk_all_frames, export_mp4,
export_sim_timeseries_csv, export_sim_timeseries_png, export_sim_timeseries_html,
export_qpu_timeseries_csv, export_qpu_timeseries_png, export_qpu_timeseries_html,
)
from .handlers import build_qubit_plot
if TYPE_CHECKING:
pass
# ---------------------------------------------------------------------------
# Placeholder builders for preview areas
# ---------------------------------------------------------------------------
def _build_empty_figure(message: str = "") -> go.Figure:
"""Build an empty placeholder figure with optional message."""
fig = go.Figure()
if message:
fig.add_annotation(
text=message,
x=0.5, y=0.5, showarrow=False,
font=dict(size=16, color="#888")
)
fig.update_layout(
margin=dict(l=20, r=20, t=40, b=20),
paper_bgcolor="#ffffff",
plot_bgcolor="#ffffff",
height=400,
)
fig.update_xaxes(visible=False)
fig.update_yaxes(visible=False)
return fig
# ---------------------------------------------------------------------------
# Main UI Builder
# ---------------------------------------------------------------------------
def build_ui():
"""Render the EM UI inside the host layout."""
_server = get_server()
if _server is None or not state.bound or not ctrl.bound:
raise RuntimeError('Call set_server(server) before build_ui().')
with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
# Upload Dialog
with vuetify3.VDialog(v_model=("show_upload_dialog", False), max_width="500px"):
with vuetify3.VCard():
vuetify3.VCardTitle("Upload Geometry")
with vuetify3.VCardText(classes="py-1 px-2"):
vuetify3.VFileInput(
show_size=True,
label="Select geometry file",
accept=".vtp,.vtk,.glb,.stl",
update_binary=("uploaded_file_info", 1),
)
with vuetify3.VCardActions():
vuetify3.VSpacer()
vuetify3.VBtn("Cancel", click="show_upload_dialog = false")
# Snackbars for status messages
vuetify3.VSnackbar(
v_model=("show_upload_status", False),
children=["{{ upload_status_message }}"],
timeout=4000,
location="bottom right",
color="primary",
variant="tonal",
)
vuetify3.VSnackbar(
v_model=("show_export_status", False),
children=["{{ export_status_message }}"],
timeout=4000,
location="bottom right",
color="primary",
variant="tonal",
)
with vuetify3.VRow(no_gutters=True, classes="fill-height"):
# Left Column - Configuration
with vuetify3.VCol(cols=5, classes="pa-1 d-flex flex-column"):
_build_overview_card()
_build_geometry_card()
_build_excitation_card()
_build_material_card()
_build_time_card()
_build_meshing_card()
_build_backends_card()
_build_output_preferences_card()
_build_run_buttons()
_build_job_upload_card()
vuetify3.VSpacer()
_build_reset_button()
# Right Column - Visualization
with vuetify3.VCol(cols=7, classes="pa-1 d-flex flex-column"):
_build_output_config_card()
_build_main_plot_area()
_build_qpu_plot_area()
_build_no_geometry_placeholder()
_build_status_console()
# Floating status window
_build_status_window()
def _build_overview_card():
"""Build the Overview/Introduction card."""
with vuetify3.VCard(classes="mb-1", style=("overview_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Overview", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
vuetify3.VSelect(
label="Select a problem",
v_model=("problem_selection", None),
items=(
"problem_options",
[
"Propagation in a given medium (no bodies)",
"Scattering from a perfectly conducting body",
],
),
placeholder="Select a problem",
density="compact",
color="primary",
)
def _build_geometry_card():
"""Build the Geometry configuration card."""
with vuetify3.VCard(classes="mb-1", style=("geometry_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Geometry", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
vuetify3.VSelect(
label="Select",
v_model=("geometry_selection", None),
items=("geometry_options",),
placeholder="Select",
density="compact",
color="primary",
)
with vuetify3.VContainer(v_if="geometry_selection === 'Square Metallic Body'", classes="pa-0 mt-2"):
with vuetify3.VRow(dense=True):
with vuetify3.VCol():
with vuetify3.VTooltip("Square hole edge length s in domain units [0,1]. Must be ≤ 1. UI-only.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
v_model=("hole_size_edge", 0.2),
label="Hole Edge Length [0 - 1]",
type="number",
step=0.05,
min=0,
density="compact",
color="primary",
)
with vuetify3.VCol():
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"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
v_model=("hole_center_pair", "(0.5, 0.5)"),
label="Hole Center (X, Y)",
density="compact",
color="primary",
)
with vuetify3.VRow(dense=True, classes="mt-1"):
with vuetify3.VCol(cols=12):
vuetify3.VSwitch(
v_model=("hole_snap", True),
label="Snap edges to nearest grid lines",
color="primary",
inset=True,
density="compact",
)
vuetify3.VAlert(
v_if="hole_error_message",
type="error",
variant="tonal",
density="compact",
children=["{{ hole_error_message }}"],
classes="mt-1",
)
def _build_excitation_card():
"""Build the Excitation/Initial State configuration card."""
with vuetify3.VCard(classes="mb-1", style=("excitation_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Excitation: Initial State", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
with vuetify3.VRow(classes="d-flex align-center", dense=True, no_gutters=True):
with vuetify3.VCol(classes="flex-grow-1"):
vuetify3.VSelect(
label="Select",
v_model=("dist_type", None),
items=("dist_type_options", ["None", "Delta", "Gaussian"]),
placeholder="Select",
density="compact",
color="primary",
)
with vuetify3.VCol(cols="auto", classes="ml-1"):
with vuetify3.VTooltip("Show or hide configuration for the selected excitation", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
with vuetify3.VBtn(
icon=True,
density="compact",
variant="text",
click="excitation_config_open = !excitation_config_open",
disabled=("!dist_type", False),
v_bind="props",
):
vuetify3.VIcon("mdi-cog", color=("excitation_config_open ? 'primary' : 'grey'",))
trame_html.Span("Toggle parameter inputs for the chosen excitation")
# Delta config
with vuetify3.VExpandTransition():
with vuetify3.VSheet(
v_if="excitation_config_open && dist_type === 'Delta'",
classes="pa-2 mt-1 rounded-lg",
style="background-color: rgba(95,37,159,0.03); border: 1px solid rgba(95,37,159,0.15);",
):
with vuetify3.VTooltip("Impulse position (x, y) in [0,1]. Example: (0.6, 0.6).", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
v_model=("peak_pair", "(0.5, 0.5)"),
label="Peak (x, y) in [0,1]",
density="compact",
color="primary",
)
# Gaussian config
with vuetify3.VExpandTransition():
with vuetify3.VSheet(
v_if="excitation_config_open && dist_type === 'Gaussian'",
classes="pa-2 mt-1 rounded-lg",
style="background-color: rgba(95,37,159,0.03); border: 1px solid rgba(95,37,159,0.15);",
):
with vuetify3.VRow(dense=True):
with vuetify3.VCol():
with vuetify3.VTooltip("Gaussian center μ (x, y) in [0,1]. Example: (0.5, 0.5).", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
v_model=("mu_pair", "(0.5, 0.5)"),
label="Mu (x, y) in [0,1]",
density="compact",
color="primary",
)
with vuetify3.VRow(dense=True, classes="mt-1"):
with vuetify3.VCol():
with vuetify3.VTooltip("Gaussian spread σx in [0,1] of domain length.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
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",
)
with vuetify3.VCol():
with vuetify3.VTooltip("Gaussian spread σy in [0,1] of domain length.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
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",
)
vuetify3.VAlert(v_if="excitation_error_message", type="error", variant="tonal", density="compact", children=["{{ excitation_error_message }}"], classes="mt-1")
vuetify3.VAlert(
v_if="excitation_info_message",
type="info",
variant="tonal",
density="compact",
children=["{{ excitation_info_message }}"],
classes="mt-1",
style="white-space: pre-line;",
)
def _build_material_card():
"""Build the Material Properties card."""
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
with vuetify3.VCardTitle("Material Properties (Medium)", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
with vuetify3.VRow(dense=True):
with vuetify3.VCol(cols="6"):
with vuetify3.VTooltip("Relative permittivity. ε_r = ε / ε₀. Default 1.0 (free space).", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(v_bind="props", v_model=("coeff_permittivity", 1.0), label="Permittivity (εr)", type="number", step="0.1", density="compact", color="primary")
with vuetify3.VCol(cols="6"):
with vuetify3.VTooltip("Relative permeability. μ_r = μ / μ₀. Default 1.0 (non-magnetic).", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(v_bind="props", v_model=("coeff_permeability", 1.0), label="Permeability (μr)", type="number", step="0.1", density="compact", color="primary")
def _build_time_card():
"""Build the Time configuration card."""
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
with vuetify3.VCardTitle("Time", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
with vuetify3.VTooltip("Sets the total duration for the simulation to run.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
v_model=("T", 1.0),
label="Total Time (T)",
type="number",
step="0.1",
density="compact",
color="primary",
)
def _build_meshing_card():
"""Build the Meshing card with qubit plot hover."""
with vuetify3.VCard(classes="mb-1", style=("meshing_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Meshing", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
with vuetify3.Template(v_slot_activator="{ props }"):
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",
):
vuetify3.Template(
v_slot_thumb_label="{ modelValue }",
children=["{{ modelValue === null ? 'Select' : grid_size_labels[modelValue] }}"],
)
# Hover content: enlarged Plotly graph
with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 644px;"):
qubit_fig_widget = plotly_widgets.Figure(
figure=build_qubit_plot(int(state.nx or 16)),
responsive=True,
style="width: 616px; height: 364px; min-height: 364px;",
)
ctrl.qubit_plot_update = qubit_fig_widget.update
def _build_backends_card():
"""Build the Backends selection card."""
with vuetify3.VCard(classes="mb-1", style=("backend_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Backends", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
with vuetify3.VRow(dense=True, classes="mb-2"):
with vuetify3.VCol():
vuetify3.VAlert(
type="info",
color="primary",
variant="tonal",
density="compact",
children=[
"Selected: ",
"{{ backend_type || '—' }}",
" - ",
"{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}",
],
)
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
with vuetify3.VList(density="compact"):
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
with vuetify3.VList(density="compact"):
vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
vuetify3.VListItem(title="Statevector Estimator", click="backend_type = 'Simulator'; selected_simulator = 'Statevector Estimator'")
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
with vuetify3.VList(density="compact"):
vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
def _build_output_preferences_card():
"""Build the Output Preferences card (appears after backend selection)."""
with vuetify3.VCard(v_if="backend_type", classes="mb-0", style=("output_card_style", "font-size: 0.8rem;")):
with vuetify3.VCardTitle("Output Preferences", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
vuetify3.VCardSubtitle("Select Δt intervals for plotting output snapshots", classes="text-caption font-weight-bold mt-1", style="font-size: 0.75rem;")
with vuetify3.VTooltip("Snapshot interval (Δt). Solver runs at fixed 0.1 s; frames are saved every Δt.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(v_bind="props", v_model=("dt_user", 0.1), label="Δt", type="number", step="0.1", density="compact", color="primary", classes="mt-1")
vuetify3.VAlert(v_if="temporal_warning", type="warning", variant="tonal", density="compact", children=["{{ temporal_warning }}"], classes="mt-1")
# IBM QPU monitor options (single field, single position ONLY)
with vuetify3.VContainer(v_if="backend_type === 'QPU' && selected_qpu === 'IBM QPU'", classes="pa-0 mt-2"):
vuetify3.VAlert(
type="info",
variant="tonal",
density="compact",
children=["IBM QPU: Only ONE field component and ONE monitor position supported."],
classes="mb-2",
)
with vuetify3.VRow(dense=True, classes="mb-1 align-center"):
with vuetify3.VCol(cols=4, sm=3, md=3):
vuetify3.VSelect(
label="Field",
v_model=("qpu_field_components", "Ez"),
items=("ibm_qpu_field_options", ["Ez", "Hx", "Hy"]),
density="compact",
color="primary",
hide_details=True,
style="max-width: 160px;",
)
with vuetify3.VCol(cols=8, sm=9, md=9):
vuetify3.VTextField(
label="Monitor position (x, y) in [0,1]",
v_model=("qpu_monitor_samples", "(0.5, 0.5)"),
density="compact",
color="primary",
hide_details=True,
style="max-width: 320px;",
hint="Single position only for IBM QPU",
)
vuetify3.VAlert(
v_if="qpu_monitor_sample_info",
type="info",
variant="tonal",
density="compact",
children=["{{ qpu_monitor_sample_info }}"],
classes="mb-1",
style="white-space: pre-line;",
)
# Statevector Estimator monitor options (supports multiple fields and positions)
with vuetify3.VContainer(v_if="backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator'", classes="pa-0 mt-2"):
with vuetify3.VRow(dense=True, classes="mb-1 align-center"):
with vuetify3.VCol(cols=4, sm=3, md=3):
vuetify3.VSelect(
label="Field",
v_model=("qpu_field_components", "Ez"),
items=("qpu_field_options", ["All", "Ez", "Hx", "Hy"]),
density="compact",
color="primary",
hide_details=True,
style="max-width: 160px;",
)
with vuetify3.VCol(cols=8, sm=9, md=9):
vuetify3.VTextField(
label="Sample position(s) (x, y) in [0,1]",
v_model=("qpu_monitor_samples", "(0.5, 0.5)"),
density="compact",
color="primary",
hide_details=True,
style="max-width: 320px;",
)
vuetify3.VAlert(
v_if="qpu_monitor_sample_info",
type="info",
variant="tonal",
density="compact",
children=["{{ qpu_monitor_sample_info }}"],
classes="mb-1",
style="white-space: pre-line;",
)
def _build_job_upload_card():
"""Build the Job Result Upload card for loading previously saved QPU results."""
with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
with vuetify3.VCardTitle("Upload Results", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2"):
trame_html.Div(
"Retrieve completed job results from IBM or IonQ using the Job ID",
classes="text-caption text-medium-emphasis mb-2",
)
# Platform selector
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VSelect(
v_bind="props",
label="Platform",
v_model=("em_job_platform", "IBM"),
items=("['IBM', 'IonQ']",),
density="compact",
hide_details=True,
color="primary",
classes="mb-2",
prepend_icon="mdi-chip",
)
trame_html.Span("Select the quantum hardware provider (IBM or IonQ)")
# Job ID input
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
label="Job ID",
v_model=("em_job_id", ""),
density="compact",
hide_details=True,
color="primary",
classes="mb-2",
placeholder="e.g., 019b368e-6e22-7525-8512-fd16e0503673",
prepend_icon="mdi-identifier",
)
trame_html.Span("Enter the Job ID (UUID format from IBM or IonQ)")
# Field / monitor / time parameters
with vuetify3.VRow(dense=True, classes="mb-2"):
with vuetify3.VCol(cols=6):
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VSelect(
v_bind="props",
label="Field Type",
v_model=("em_job_field_type", "Ez"),
items=("['Ez', 'Hx', 'Hy']",),
density="compact",
hide_details=True,
color="primary",
)
trame_html.Span("Select which field component to plot (Ez, Hx, Hy)")
with vuetify3.VCol(cols=6):
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
label="Monitor Point (x, y)",
v_model=("em_job_monitor_point", "(0, 0)"),
density="compact",
hide_details=True,
color="primary",
placeholder="(x, y)",
)
trame_html.Span("Integer grid indices of the monitor point")
with vuetify3.VRow(dense=True, classes="mb-2"):
with vuetify3.VCol(cols=4):
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
label="Total Time (T)",
v_model=("em_job_total_time", 1.0),
type="number",
density="compact",
hide_details=True,
color="primary",
)
trame_html.Span("Total simulation time")
with vuetify3.VCol(cols=4):
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
label="Snapshot Δt",
v_model=("em_job_snapshot_dt", 0.1),
type="number",
density="compact",
hide_details=True,
color="primary",
)
trame_html.Span("Time between snapshots")
with vuetify3.VCol(cols=4):
with vuetify3.VTooltip(location="top"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VTextField(
v_bind="props",
label="No. of Points per Direction",
v_model=("em_job_nx", 4),
type="number",
density="compact",
hide_details=True,
color="primary",
)
trame_html.Span("Grid dimension nx (grid is nx × nx)")
vuetify3.VBtn(
text="Retrieve & Generate Plot",
color="secondary",
variant="tonal",
block=True,
disabled=("!em_job_id || em_job_is_processing", True),
loading=("em_job_is_processing", False),
click=process_uploaded_em_job_result,
prepend_icon="mdi-chart-box-outline",
classes="mb-2",
)
# Success message
vuetify3.VAlert(
v_if="em_job_upload_success",
type="success",
variant="tonal",
density="compact",
closable=True,
children=["{{ em_job_upload_success }}"],
classes="mt-2",
)
# Error message
vuetify3.VAlert(
v_if="em_job_upload_error",
type="error",
variant="tonal",
density="compact",
closable=True,
children=["{{ em_job_upload_error }}"],
classes="mt-2",
)
def _build_run_buttons():
"""Build the Run and Stop buttons row."""
with vuetify3.VRow(dense=True, classes="mb-2"):
with vuetify3.VCol(cols=9):
with vuetify3.VTooltip("Starts the quantum simulation with the specified parameters.", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(
v_bind="props",
text=("run_button_text", "RUN!"),
click=run_simulation_only,
color="primary",
block=True,
disabled=("is_running || run_button_text === 'Successful!' || !geometry_selection || !dist_type || !!temporal_warning || nx === null || !backend_type", False),
)
with vuetify3.VCol(cols=3):
with vuetify3.VTooltip("Stop the running simulation", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(
v_bind="props",
text="Stop",
click=stop_simulation_handler,
color="error",
block=True,
disabled=("stop_button_disabled", True),
)
def _build_reset_button():
"""Build the Reset button."""
with trame_html.Div(style="flex: 0 0 auto;"):
with vuetify3.VTooltip("Reset all parameters to their default values", location="bottom", color="primary"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(
v_bind="props",
text="Reset",
click=reset_to_defaults,
color="secondary",
block=True,
)
def _build_output_config_card():
"""Build the Output Configuration card (appears after simulation, not for QPU)."""
with vuetify3.VCard(v_if="simulation_has_run && backend_type !== 'QPU' && !(backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator')", classes="mb-1", style="font-size: 0.8rem;"):
with vuetify3.VCardSubtitle("Output Configuration", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px; font-weight: 600; color: #1A1A1A;"):
with vuetify3.VCardText(classes="py-1 px-2", style="color: #1A1A1A;"):
with vuetify3.VRadioGroup(v_model=("output_type", "Surface Plot"), row=True, density="compact", color="primary"):
vuetify3.VRadio(label="Surface", value="Surface Plot", style="font-weight: 500;")
vuetify3.VRadio(label="Time Series", value="Time Series Plot", style="font-weight: 500;")
# Surface Plot options
with vuetify3.VContainer(v_if="output_type === 'Surface Plot'", classes="pa-0"):
vuetify3.VSelect(v_model=("surface_field", "Ez"), items=("surface_field_options", ["Ez", "Hx", "Hy"]), label="Field Component", density="compact", color="primary", style="font-weight: 500;")
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(v_bind="props", text="DOWNLOAD", color="primary", variant="tonal", block=True, classes="mt-1")
with vuetify3.VList(density="compact"):
vuetify3.VListSubheader("VTK")
vuetify3.VListItem(title="Current frame (VTK)", prepend_icon="mdi-download", click=export_vtk)
vuetify3.VListItem(title="All frames (VTK sequence)", prepend_icon="mdi-download-multiple", click=export_vtk_all_frames)
vuetify3.VDivider()
vuetify3.VListItem(title="Animation (MP4)", prepend_icon="mdi-movie", click=export_mp4)
# Time Series options
with vuetify3.VContainer(v_if="output_type === 'Time Series Plot'", classes="pa-0"):
vuetify3.VSelect(v_model=("timeseries_field", "Ez"), items=("timeseries_field_options", ["All", "Ez", "Hx", "Hy"]), label="Field Component", density="compact", color="primary", style="font-weight: 500;")
vuetify3.VTextarea(
v_model=("timeseries_points", "(0.5, 0.5)"),
label="Monitor Position(s) (x, y) in [0,1]",
hint="e.g., (0.50, 0.50) or multiple comma-separated pairs",
rows=2,
auto_grow=True,
color="primary",
style="font-weight: 500;",
)
vuetify3.VAlert(
v_if="timeseries_point_info",
type="info",
variant="tonal",
density="compact",
children=["{{ timeseries_point_info }}"],
classes="mt-1",
style="white-space: pre-line;",
)
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(v_bind="props", text="DOWNLOAD", color="primary", variant="tonal", block=True, classes="mt-1")
with vuetify3.VList(density="compact"):
vuetify3.VListItem(title="Download CSV", prepend_icon="mdi-download", click=export_sim_timeseries_csv)
vuetify3.VListItem(title="Download PNG", prepend_icon="mdi-image", click=export_sim_timeseries_png)
vuetify3.VListItem(title="Download HTML", prepend_icon="mdi-file-html", click=export_sim_timeseries_html)
def _build_main_plot_area():
"""Build the main plot area (PyVista for Simulator)."""
with vuetify3.VCard(
v_if="geometry_selection && ((backend_type !== 'QPU' && !(backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator')) || !qpu_ts_ready) && !(is_running && (backend_type === 'QPU' || (backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator')))",
classes="mb-1 flex-grow-1 d-flex flex-column",
style="min-height: 0;",
):
# Running indicator
with vuetify3.VContainer(v_if="is_running", fluid=True, classes="fill-height d-flex flex-column align-center justify-center"):
vuetify3.VProgressCircular(indeterminate=True, size=64, color="primary")
vuetify3.VCardSubtitle("Running simulation...", classes="mt-4")
# Geometry preview (no excitation selected yet)
with vuetify3.VContainer(
v_if="!is_running && geometry_selection && !dist_type",
fluid=True,
classes="pa-0 flex-grow-1 d-flex align-center justify-center",
style="width: 100%; min-height: 560px;",
):
geometry_preview_widget = plotly_widgets.Figure(
figure=_build_geometry_placeholder("Select a geometry to preview."),
responsive=True,
style="width: 100%; height: 100%;",
)
ctrl.geometry_preview_update = geometry_preview_widget.update
# Excitation preview (before simulation runs)
with vuetify3.VContainer(
v_if="!is_running && dist_type && !simulation_has_run",
fluid=True,
classes="pa-0 flex-grow-1 d-flex align-center justify-center",
style="width: 100%; min-height: 580px;",
):
excitation_preview_widget = plotly_widgets.Figure(
figure=_build_excitation_placeholder("Select an excitation to preview."),
responsive=True,
style="width: 100%; height: 100%;",
)
ctrl.excitation_preview_update = excitation_preview_widget.update
# Surface Plot: PyVista view
with vuetify3.VContainer(v_if="!is_running && simulation_has_run && output_type === 'Surface Plot'", fluid=True, classes="pa-0", style=("pyvista_view_style", "aspect-ratio: 1 / 1; width: 100%;")):
view = plotter_ui(plotter)
ctrl.view_update = view.update
# Time Series: Plotly figure
with vuetify3.VContainer(v_if="!is_running && simulation_has_run && output_type === 'Time Series Plot'", fluid=True, classes="d-flex align-center justify-center pa-2", style="overflow: hidden;"):
sim_ts = plotly_widgets.Figure(
figure=go.Figure(layout=dict(width=900, height=660)),
responsive=False,
style="width: 900px; height: 660px;",
)
ctrl.sim_ts_update = sim_ts.update
# Time slider for surface plot
with vuetify3.VContainer(v_if="simulation_has_run && output_type === 'Surface Plot' && backend_type !== 'QPU' && !(backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator')", fluid=True, classes="pa-0 mt-2"):
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")
def _build_qpu_plot_area():
"""Build the QPU plot area (Plotly time series)."""
with vuetify3.VCard(v_if="geometry_selection && (backend_type === 'QPU' || (backend_type === 'Simulator' && selected_simulator === 'Statevector Estimator')) && (is_running || qpu_ts_ready)", classes="flex-grow-1", style="min-height: 0;"):
# Running indicator
with vuetify3.VContainer(v_if="is_running", fluid=True, classes="fill-height d-flex flex-column align-center justify-center"):
vuetify3.VProgressCircular(indeterminate=True, size=64, color="primary")
vuetify3.VCardSubtitle("Running {{ backend_type === 'QPU' ? selected_qpu : selected_simulator }}...", classes="mt-4")
# QPU timeseries with toolbar
with vuetify3.VContainer(v_if="!is_running && qpu_ts_ready", fluid=True, classes="pa-2"):
with vuetify3.VToolbar(density="compact", flat=True, color="transparent", classes="px-0"):
vuetify3.VSelect(
label="Component",
v_model=("qpu_plot_filter", "All"),
items=("qpu_plot_field_options", ["All"]),
density="compact",
color="primary",
hide_details=True,
style="max-width: 180px; margin-right: 8px;",
disabled=("!qpu_ts_ready", True),
update_modelValue=(ctrl.qpu_set_plot_filter, "[$event]")
)
vuetify3.VSelect(
label="Position",
v_model=("qpu_plot_position_filter", "All positions"),
items=("qpu_plot_position_options", ["All positions"]),
density="compact",
color="primary",
hide_details=True,
style="max-width: 200px; margin-right: 8px;",
disabled=("!qpu_ts_ready", True),
update_modelValue=(ctrl.qpu_set_plot_position_filter, "[$event]")
)
vuetify3.VSpacer()
vuetify3.VBtn(
text="Clear Selection",
color="secondary",
variant="text",
click=ctrl.on_qpu_ts_clear,
disabled=("!qpu_ts_ready", True),
)
with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
with vuetify3.Template(v_slot_activator="{ props }"):
vuetify3.VBtn(
v_bind="props",
text="DOWNLOAD",
color="primary",
variant="tonal",
disabled=("!qpu_ts_ready", True),
)
with vuetify3.VList(density="compact"):
vuetify3.VListItem(title="Download CSV", prepend_icon="mdi-download", click=export_qpu_timeseries_csv, disabled=("!qpu_ts_ready", True))
vuetify3.VListItem(title="Download PNG", prepend_icon="mdi-image", click=export_qpu_timeseries_png, disabled=("!qpu_ts_ready", True))
vuetify3.VListItem(title="Download HTML", prepend_icon="mdi-file-html", click=export_qpu_timeseries_html, disabled=("!qpu_ts_ready", True))
trame_html.Div(style="height: 6px;")
# Plotly figure for QPU timeseries
qpu_ts_widget = plotly_widgets.Figure(
figure=go.Figure(layout=dict(width=900, height=660)),
responsive=False,
style=("qpu_plot_style", "display: none; width: 900px; height: 660px; margin: 0 auto;"),
click=ctrl.on_qpu_ts_click,
)
ctrl.qpu_ts_update = qpu_ts_widget.update
def _build_no_geometry_placeholder():
"""Build placeholder when no geometry is selected."""
with vuetify3.VContainer(v_if="!geometry_selection", fluid=True, classes="flex-grow-1 d-flex align-center justify-center text-medium-emphasis"):
vuetify3.VCardText("Select a geometry to display the preview and results.")
def _build_status_console():
"""Build the Status/Console card."""
with vuetify3.VCard(classes="mt-2", style="font-size: 0.8rem;"):
with vuetify3.VCardTitle("Status", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
pass
with vuetify3.VCardText(classes="py-1 px-2", style="height: 150px; overflow-y: auto; background-color: #f5f5f5; font-family: monospace;"):
vuetify3.VTextarea(
v_model=("console_output", ""),
readonly=True,
auto_grow=False,
rows=6,
variant="plain",
hide_details=True,
style="font-family: monospace; width: 100%; height: 100%;"
)
def _build_status_window():
"""Build the floating status window (bottom right)."""
with vuetify3.VCard(
v_if="status_visible",
style="position: fixed; bottom: 16px; right: 16px; z-index: 1000; min-width: 320px; max-width: 450px;",
elevation=8
):
with vuetify3.VCardTitle(classes="d-flex align-center", style="font-size: 0.95rem; padding: 8px 12px;"):
vuetify3.VIcon("mdi-information-outline", size="small", classes="mr-2")
trame_html.Span("Simulation Status")
vuetify3.VSpacer()
vuetify3.VBtn(
icon="mdi-close",
size="x-small",
variant="text",
click="status_visible = false"
)
vuetify3.VDivider()
with vuetify3.VCardText(classes="py-2 px-3"):
vuetify3.VAlert(
type=("status_type", "info"),
variant="tonal",
density="compact",
children=["{{ status_message }}"]
)
with vuetify3.VContainer(v_if="show_progress", classes="pa-0 mt-2"):
vuetify3.VProgressLinear(
model_value=("simulation_progress", 0),
color="primary",
height=6,
striped=True
)
trame_html.Div(
"{{ simulation_progress }}% complete",
classes="text-caption text-center mt-1",
style="font-size: 0.75rem;"
)