diff --git a/src/python/impactx/dashboard/Input/__init__.py b/src/python/impactx/dashboard/Input/__init__.py index e69de29bb..a0875b1bb 100644 --- a/src/python/impactx/dashboard/Input/__init__.py +++ b/src/python/impactx/dashboard/Input/__init__.py @@ -0,0 +1,14 @@ +from trame.widgets import vuetify as vuetify + +from ..trame_setup import setup_server +from .defaults import DashboardDefaults +from .generalFunctions import generalFunctions +from .trameFunctions import TrameFunctions + +__all__ = [ + "vuetify", + "DashboardDefaults", + "TrameFunctions", + "generalFunctions", + "setup_server", +] diff --git a/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py b/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py index 83b5e0710..924bfeddb 100644 --- a/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py +++ b/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py @@ -1,34 +1,7 @@ -from trame.widgets import vuetify - -from ...Input.trameFunctions import TrameFunctions -from ...trame_setup import setup_server -from ..generalFunctions import generalFunctions +from .. import TrameFunctions, setup_server, vuetify server, state, ctrl = setup_server() -# ----------------------------------------------------------------------------- -# Default State Variables -# ----------------------------------------------------------------------------- - -state.csr_bins = generalFunctions.get_default("csr_bins", "default_values") -state.csr_bins_error_message = "" - -# ----------------------------------------------------------------------------- -# -# ----------------------------------------------------------------------------- - - -@state.change("csr_bins") -def on_csr_bins_change(csr_bins, **kwargs): - error_message = generalFunctions.validate_against(csr_bins, "int", ["positive"]) - state.csr_bins_error_message = error_message - generalFunctions.update_simulation_validation_status() - - -# ----------------------------------------------------------------------------- -# UI -# ----------------------------------------------------------------------------- - class csrConfiguration: @staticmethod @@ -38,35 +11,16 @@ def card(): """ with vuetify.VCard(v_show="csr", style="width: 170px;"): - with vuetify.VCardTitle("CSR"): - vuetify.VSpacer() - TrameFunctions.create_refresh_button( - lambda: generalFunctions.reset_inputs("csr") - ) - vuetify.VIcon( - "mdi-information", - classes="ml-2", - click=lambda: generalFunctions.documentation("CSR"), - style="color: #00313C;", - ) - vuetify.VDivider() + TrameFunctions.input_section_header("CSR") with vuetify.VCardText(): with vuetify.VRow(classes="my-0"): with vuetify.VCol(classes="py-0"): - vuetify.VSelect( + TrameFunctions.select( label="Particle Shape", - v_model=("particle_shape",), - items=([1, 2, 3],), - dense=True, ) with vuetify.VRow(classes="my-0"): with vuetify.VCol(classes="py-0"): - vuetify.VTextField( + TrameFunctions.text_field( label="CSR Bins", - v_model=("csr_bins",), - error_messages=("csr_bins_error_message",), - type="number", - dense=True, - step=generalFunctions.get_default("csr_bins", "steps"), - __properties=["step"], + input=(ctrl.input_change, "['csr_bins']"), ) diff --git a/src/python/impactx/dashboard/Input/defaults.py b/src/python/impactx/dashboard/Input/defaults.py index 856833ac7..97a34706c 100644 --- a/src/python/impactx/dashboard/Input/defaults.py +++ b/src/python/impactx/dashboard/Input/defaults.py @@ -17,16 +17,17 @@ class DashboardDefaults: "mass_MeV": 0.51099895, "npart": 1000, "kin_energy": 2e3, + "kin_energy_MeV": 2e3, "kin_energy_unit": "MeV", "bunch_charge_C": 1e-9, } - DISTRIBUTION = { - "selected_distribution": "Waterbag", - "selected_distribution_type": "Twiss", + DISTRIBUTION_PARAMETERS = { + "distribution": "Waterbag", + "distribution_type": "Twiss", } - LATTICE = { + LATTICE_CONFIGURATION = { "selected_lattice_list": [], "selected_lattice": None, } @@ -70,13 +71,28 @@ class DashboardDefaults: DEFAULT_VALUES = { **SELECTION, **INPUT_PARAMETERS, - **DISTRIBUTION, - **LATTICE, + **DISTRIBUTION_PARAMETERS, + **LATTICE_CONFIGURATION, **SPACE_CHARGE, **CSR, **LISTS, } + TYPES = { + "npart": "int", + "kin_energy": "float", + "bunch_charge_C": "float", + "mass_MeV": "float", + "charge_qe": "int", + "csr_bins": "int", + } + + VALIDATION_CONDITION = { + "charge_qe": ["non_zero"], + "mass_MeV": ["positive"], + "csr_bins": ["positive"], + } + # If a parameter is not included in the dictionary, default step amount is 1. STEPS = { "mass_MeV": 0.1, diff --git a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py index a37b8a32f..cd664de92 100644 --- a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py +++ b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py @@ -9,13 +9,10 @@ import inspect from distribution_input_helpers import twiss -from trame.widgets import vuetify from impactx import distribution -from ...Input.trameFunctions import TrameFunctions -from ...trame_setup import setup_server -from ..generalFunctions import generalFunctions +from .. import TrameFunctions, generalFunctions, setup_server, vuetify from .distributionFunctions import DistributionFunctions server, state, ctrl = setup_server() @@ -24,39 +21,28 @@ # Helpful # ----------------------------------------------------------------------------- -DISTRIBUTIONS_MODULE_NAME = distribution - -state.listOfDistributions = generalFunctions.select_classes(DISTRIBUTIONS_MODULE_NAME) -state.listOfDistributionsAndParametersAndDefault = ( - generalFunctions.class_parameters_with_defaults(DISTRIBUTIONS_MODULE_NAME) +DISTRIBUTION_MODULE_NAME = distribution +DISTRIBUTION_LIST = generalFunctions.select_classes(DISTRIBUTION_MODULE_NAME) +DISTRIBUTION_PARAMETERS_AND_DEFAULTS = generalFunctions.class_parameters_with_defaults( + DISTRIBUTION_MODULE_NAME ) -# ----------------------------------------------------------------------------- -# Defaults -# ----------------------------------------------------------------------------- - -state.selected_distribution = generalFunctions.get_default( - "selected_distribution", "default_values" -) -state.selected_distribution_type = generalFunctions.get_default( - "selected_distribution_type", "default_values" -) state.selected_distribution_parameters = [] -state.distributionTypeDisabled = False +state.distribution_type_disable = False # ----------------------------------------------------------------------------- # Main Functions # ----------------------------------------------------------------------------- -def populate_distribution_parameters(selected_distribution): +def populate_distribution_parameters(): """ Populates distribution parameters based on the selected distribution. :param selected_distribution (str): The name of the selected distribution whose parameters need to be populated. """ - if state.selected_distribution_type == "Twiss": + if state.distribution_type == "Twiss": sig = inspect.signature(twiss) state.selected_distribution_parameters = [ { @@ -77,10 +63,8 @@ def populate_distribution_parameters(selected_distribution): ] else: # when type == 'Quadratic Form' - selected_distribution_parameters = ( - state.listOfDistributionsAndParametersAndDefault.get( - selected_distribution, [] - ) + selected_distribution_parameters = DISTRIBUTION_PARAMETERS_AND_DEFAULTS.get( + state.distribution, [] ) state.selected_distribution_parameters = [ @@ -103,26 +87,6 @@ def populate_distribution_parameters(selected_distribution): return state.selected_distribution_parameters -def update_distribution_parameters( - parameterName, parameterValue, parameterErrorMessage -): - """ - Updates the value of a distribution parameter and its error message. - - :param parameterName (str): The name of the parameter to update. - :param parameterValue: The new value for the parameter. - :param parameterErrorMessage: The error message related to the parameter's value. - """ - - for param in state.selected_distribution_parameters: - if param["parameter_name"] == parameterName: - param["parameter_default_value"] = parameterValue - param["parameter_error_message"] = parameterErrorMessage - - generalFunctions.update_simulation_validation_status() - state.dirty("selected_distribution_parameters") - - # ----------------------------------------------------------------------------- # Write to file functions # ----------------------------------------------------------------------------- @@ -134,10 +98,10 @@ def distribution_parameters(): initialized with the appropriate parameters provided by the user. """ - distribution_name = state.selected_distribution + distribution_name = state.distribution parameters = DistributionFunctions.convert_distribution_parameters_to_valid_type() - if state.selected_distribution_type == "Twiss": + if state.distribution_type == "Twiss": twiss_params = twiss(**parameters) distr = getattr(distribution, distribution_name)(**twiss_params) else: @@ -151,20 +115,20 @@ def distribution_parameters(): # ----------------------------------------------------------------------------- -@state.change("selected_distribution") -def on_distribution_name_change(selected_distribution, **kwargs): - if selected_distribution == "Thermal": - state.selected_distribution_type = "Quadratic Form" - state.distributionTypeDisabled = True - state.dirty("selected_distribution_type") +@state.change("distribution") +def on_distribution_name_change(distribution, **kwargs): + if distribution == "Thermal": + state.distribution_type = "Quadratic Form" + state.distribution_type_disable = True + state.dirty("distribution_type") else: - state.distributionTypeDisabled = False - populate_distribution_parameters(selected_distribution) + state.distribution_type_disable = False + populate_distribution_parameters() -@state.change("selected_distribution_type") +@state.change("distribution_type") def on_distribution_type_change(**kwargs): - populate_distribution_parameters(state.selected_distribution) + populate_distribution_parameters() @ctrl.add("updateDistributionParameters") @@ -172,7 +136,13 @@ def on_distribution_parameter_change(parameter_name, parameter_value, parameter_ parameter_value, input_type = generalFunctions.determine_input_type(parameter_value) error_message = generalFunctions.validate_against(parameter_value, parameter_type) - update_distribution_parameters(parameter_name, parameter_value, error_message) + for param in state.selected_distribution_parameters: + if param["parameter_name"] == parameter_name: + param["parameter_default_value"] = parameter_value + param["parameter_error_message"] = error_message + + generalFunctions.update_simulation_validation_status() + state.dirty("selected_distribution_parameters") # ----------------------------------------------------------------------------- @@ -192,47 +162,29 @@ def card(): """ with vuetify.VCard(style="width: 340px; height: 300px"): - with vuetify.VCardTitle("Distribution Parameters"): - vuetify.VSpacer() - TrameFunctions.create_refresh_button( - lambda: generalFunctions.reset_inputs("distribution") - ) - vuetify.VIcon( - "mdi-information", - style="color: #00313C;", - click=lambda: generalFunctions.documentation("BeamDistributions"), - ) - vuetify.VDivider() + TrameFunctions.input_section_header("Distribution Parameters") with vuetify.VCardText(): with vuetify.VRow(): with vuetify.VCol(cols=6): - vuetify.VCombobox( + TrameFunctions.select( label="Select Distribution", - v_model=("selected_distribution",), - items=("listOfDistributions",), - dense=True, + v_model_name="distribution", + items=(DISTRIBUTION_LIST,), ) with vuetify.VCol(cols=6): - vuetify.VSelect( - v_model=("selected_distribution_type",), + TrameFunctions.select( label="Type", - items=( - generalFunctions.get_default( - "distribution_type_list", "default_values" - ), - ), - dense=True, - disabled=("distributionTypeDisabled",), + v_model_name="distribution_type", + disabled=("distribution_type_disable",), ) with vuetify.VRow(classes="my-2"): for i in range(3): with vuetify.VCol(cols=4, classes="py-0"): with vuetify.VRow( - v_for="(parameter, index) in selected_distribution_parameters" + v_for="(parameter, index) in selected_distribution_parameters", + v_if=f"index % 3 == {i}", ): - with vuetify.VCol( - v_if=f"index % 3 == {i}", classes="py-1" - ): + with vuetify.VCol(classes="py-1"): vuetify.VTextField( label=("parameter.parameter_name",), v_model=("parameter.parameter_default_value",), diff --git a/src/python/impactx/dashboard/Input/generalFunctions.py b/src/python/impactx/dashboard/Input/generalFunctions.py index 49280e8ec..125b360c8 100644 --- a/src/python/impactx/dashboard/Input/generalFunctions.py +++ b/src/python/impactx/dashboard/Input/generalFunctions.py @@ -30,11 +30,11 @@ def documentation(section_name): :param section_name (str): The name of the documentation section to open. """ url_dict = { - "LatticeElements": "https://impactx.readthedocs.io/en/latest/usage/python.html#lattice-elements", - "BeamDistributions": "https://impactx.readthedocs.io/en/latest/usage/python.html#initial-beam-distributions", - "pythonParameters": "https://impactx.readthedocs.io/en/latest/usage/python.html#general", - "space_charge_documentation": "https://impactx.readthedocs.io/en/latest/usage/parameters.html#space-charge", - "CSR": "https://impactx.readthedocs.io/en/latest/usage/parameters.html#coherent-synchrotron-radiation-csr", + "input_parameters": "https://impactx.readthedocs.io/en/latest/usage/python.html#general", + "lattice_configuration": "https://impactx.readthedocs.io/en/latest/usage/python.html#lattice-elements", + "distribution_parameters": "https://impactx.readthedocs.io/en/latest/usage/python.html#initial-beam-distributions", + "space_charge": "https://impactx.readthedocs.io/en/latest/usage/parameters.html#space-charge", + "csr": "https://impactx.readthedocs.io/en/latest/usage/parameters.html#coherent-synchrotron-radiation-csr", } url = url_dict.get(section_name) @@ -154,16 +154,18 @@ def update_simulation_validation_status(): ) # Check for errors in input card - if state.npart_validation: - error_details.append(f"Number of Particles: {state.npart_validation}") - if state.kin_energy_validation: - error_details.append(f"Kinetic Energy: {state.kin_energy_validation}") - if state.bunch_charge_C_validation: - error_details.append(f"Bunch Charge: {state.bunch_charge_C_validation}") - if state.charge_qe_validation: - error_details.append(f"Ref. Particle Charge: {state.charge_qe_validation}") - if state.mass_MeV_validation: - error_details.append(f"Ref. Particle Mass: {state.mass_MeV}") + if state.npart_error_message: + error_details.append(f"Number of Particles: {state.npart_error_message}") + if state.kin_energy_error_message: + error_details.append(f"Kinetic Energy: {state.kin_energy_error_message}") + if state.bunch_charge_C_error_message: + error_details.append(f"Bunch Charge: {state.bunch_charge_C_error_message}") + if state.charge_qe_error_message: + error_details.append( + f"Ref. Particle Charge: {state.charge_qe_error_message}" + ) + if state.mass_MeV_error_message: + error_details.append(f"Ref. Particle Mass: {state.mass_MeV_error_message}") if state.selected_lattice_list == []: error_details.append("LatticeListIsEmpty") @@ -343,9 +345,9 @@ def reset_inputs(input_section): if input_section.upper() in possible_section_names: state.update(getattr(DashboardDefaults, input_section.upper())) - if input_section == "distribution": + if input_section == "distribution_parameters": state.dirty("selected_distribution_type") - elif input_section == "lattice": + elif input_section == "lattice_configuration": state.selected_lattice_list = [] elif input_section == "space_charge": state.dirty("max_level") diff --git a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py index 7441ae284..cfe5eec01 100644 --- a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py +++ b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py @@ -6,11 +6,13 @@ License: BSD-3-Clause-LBNL """ -from trame.widgets import vuetify - -from ...Input.trameFunctions import TrameFunctions -from ...trame_setup import setup_server -from ..generalFunctions import generalFunctions +from .. import ( + DashboardDefaults, + TrameFunctions, + generalFunctions, + setup_server, + vuetify, +) from .inputFunctions import InputFunctions server, state, ctrl = setup_server() @@ -20,10 +22,13 @@ # ----------------------------------------------------------------------------- -@ctrl.add("on_input_change") -def validate_and_convert_to_correct_type( - value, desired_type, state_name, validation_name, conditions=None -): +@ctrl.add("input_change") +def validate_and_convert_to_correct_type(state_name): + value = getattr(state, state_name) + desired_type = DashboardDefaults.TYPES[state_name] + validation_name = f"{state_name}_error_message" + conditions = DashboardDefaults.VALIDATION_CONDITION.get(state_name, None) + validation_result = generalFunctions.validate_against( value, desired_type, conditions ) @@ -65,48 +70,13 @@ class InputParameters: User-Input section for beam properties. """ - def __init__(self): - state.particle_shape = generalFunctions.get_default( - "particle_shape", "default_values" - ) - state.npart = generalFunctions.get_default("npart", "default_values") - state.kin_energy = generalFunctions.get_default("kin_energy", "default_values") - state.kin_energy_MeV = state.kin_energy - state.bunch_charge_C = generalFunctions.get_default( - "bunch_charge_C", "default_values" - ) - state.kin_energy_unit = generalFunctions.get_default( - "kin_energy_unit", "default_values" - ) - state.old_kin_energy_unit = generalFunctions.get_default( - "kin_energy_unit", "default_values" - ) - state.charge_qe = generalFunctions.get_default("charge_qe", "default_values") - state.mass_MeV = generalFunctions.get_default("mass_MeV", "default_values") - - state.npart_validation = [] - state.kin_energy_validation = [] - state.bunch_charge_C_validation = [] - state.mass_MeV_validation = [] - state.charge_qe_validation = [] - def card(self): """ Creates UI content for beam properties. """ with vuetify.VCard(style="width: 340px; height: 350px"): - with vuetify.VCardTitle("Input Parameters"): - vuetify.VSpacer() - TrameFunctions.create_refresh_button( - lambda: generalFunctions.reset_inputs("input_parameters") - ) - vuetify.VIcon( - "mdi-information", - style="color: #00313C;", - click=lambda: generalFunctions.documentation("pythonParameters"), - ) - vuetify.VDivider() + TrameFunctions.input_section_header("Input Parameters") with vuetify.VCardText(): with vuetify.VRow(classes="py-2"): with vuetify.VCol(cols=6, classes="py-0"): @@ -123,95 +93,42 @@ def card(self): ) with vuetify.VRow(classes="my-2"): with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( + TrameFunctions.text_field( label="Ref. Particle Charge", - v_model=("charge_qe",), - suffix=generalFunctions.get_default("charge_qe", "units"), - type="number", - step=generalFunctions.get_default("charge_qe", "steps"), - __properties=["step"], - dense=True, - error_messages=("charge_qe_validation",), - change=( - ctrl.on_input_change, - "[$event, 'int','charge_qe','charge_qe_validation', ['non_zero']]", - ), + v_model_name="charge_qe", + input=(ctrl.input_change, "['charge_qe']"), ) with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( + TrameFunctions.text_field( label="Ref. Particle Mass", - v_model=("mass_MeV",), - suffix=generalFunctions.get_default("mass_MeV", "units"), - type="number", - step=generalFunctions.get_default("mass_MeV", "steps"), - __properties=["step"], - dense=True, - error_messages=("mass_MeV_validation",), - change=( - ctrl.on_input_change, - "[$event, 'float','mass_MeV','mass_MeV_validation', ['positive']]", - ), + v_model_name="mass_MeV", + input=(ctrl.input_change, "['mass_MeV']"), ) with vuetify.VRow(classes="my-0"): with vuetify.VCol(cols=12, classes="py-0"): - vuetify.VTextField( - v_model=("npart",), + TrameFunctions.text_field( label="Number of Particles", - error_messages=("npart_validation",), - change=( - ctrl.on_input_change, - "[$event, 'int','npart','npart_validation']", - ), - type="number", - step=generalFunctions.get_default("npart", "steps"), - __properties=["step"], - dense=True, + v_model_name="npart", + input=(ctrl.input_change, "['npart']"), ) with vuetify.VRow(classes="my-2"): with vuetify.VCol(cols=8, classes="py-0"): - vuetify.VTextField( - v_model=("kin_energy",), + TrameFunctions.text_field( label="Kinetic Energy", - error_messages=("kin_energy_validation",), - change=( - ctrl.on_input_change, - "[$event, 'float','kin_energy','kin_energy_validation']", - ), - type="number", - step=generalFunctions.get_default("kin_energy", "steps"), - __properties=["step"], - dense=True, + v_model_name="kin_energy", + input=(ctrl.input_change, "['kin_energy']"), classes="mr-2", ) with vuetify.VCol(cols=4, classes="py-0"): - vuetify.VSelect( - v_model=("kin_energy_unit",), + TrameFunctions.select( label="Unit", - items=( - generalFunctions.get_default( - "kin_energy_unit_list", "default_values" - ), - ), - change=(ctrl.kin_energy_unit_change, "[$event]"), - dense=True, + v_model_name="kin_energy_unit", + input=(ctrl.kin_energy_unit_change, "[$event]"), ) with vuetify.VRow(classes="my-2"): with vuetify.VCol(cols=12, classes="py-0"): - vuetify.VTextField( + TrameFunctions.text_field( label="Bunch Charge", - v_model=("bunch_charge_C",), - error_messages=("bunch_charge_C_validation",), - change=( - ctrl.on_input_change, - "[$event, 'float','bunch_charge_C','bunch_charge_C_validation']", - ), - type="number", - step=generalFunctions.get_default( - "bunch_charge_C", "steps" - ), - __properties=["step"], - dense=True, - suffix=generalFunctions.get_default( - "bunch_charge_C", "units" - ), + v_model_name="bunch_charge_C", + input=(ctrl.input_change, "['bunch_charge_C']"), ) diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index 9b5b80a8f..8a25f3f23 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -6,13 +6,9 @@ License: BSD-3-Clause-LBNL """ -from trame.widgets import vuetify - from impactx import elements -from ...Input.trameFunctions import TrameFunctions -from ...trame_setup import setup_server -from ..generalFunctions import generalFunctions +from .. import TrameFunctions, generalFunctions, setup_server, vuetify server, state, ctrl = setup_server() @@ -33,11 +29,8 @@ # Default # ----------------------------------------------------------------------------- -state.selected_lattice = generalFunctions.get_default( - "selected_lattice", "default_values" -) state.selected_lattice_list = [] -state.nsliceDefaultValue = generalFunctions.get_default("n_slice", "default_values") +state.nslice = "" # ----------------------------------------------------------------------------- # Main Functions @@ -220,7 +213,7 @@ def update_default_value(parameter_name, new_value): # ----------------------------------------------------------------------------- -# ContentSetup +# UI # ----------------------------------------------------------------------------- @@ -235,25 +228,16 @@ def card(): Creates UI content for lattice configuration. """ - with vuetify.VDialog(v_model=("showDialog", False), width="1200px"): - LatticeConfiguration.dialog_lattice_elementList() + with vuetify.VDialog(v_model=("showDialog", False)): + LatticeConfiguration.dialog_configuration_list() - with vuetify.VDialog(v_model=("showDialog_settings", False), width="500px"): - LatticeConfiguration.dialog_lattice_settings() + with vuetify.VDialog( + v_model=("lattice_configuration_dialog_settings", False), width="500px" + ): + LatticeConfiguration.dialog_settings() with vuetify.VCard(style="width: 696px;"): - with vuetify.VCardTitle("Lattice Configuration"): - vuetify.VSpacer() - TrameFunctions.create_refresh_button( - lambda: generalFunctions.reset_inputs("lattice") - ) - vuetify.VIcon( - "mdi-information", - classes="ml-2", - click=lambda: generalFunctions.documentation("LatticeElements"), - style="color: #00313C;", - ) - vuetify.VDivider() + TrameFunctions.input_section_header("Lattice Configuration") with vuetify.VCardText(): with vuetify.VRow(align="center", no_gutters=True): with vuetify.VCol(cols=10): @@ -276,7 +260,7 @@ def card(): with vuetify.VCol(cols="auto"): vuetify.VIcon( "mdi-cog", - click="showDialog_settings = true", + click="lattice_configuration_dialog_settings = true", ) with vuetify.VRow(): with vuetify.VCol(): @@ -293,144 +277,98 @@ def card(): click="showDialog = true", ) vuetify.VDivider() - with vuetify.VContainer(fluid=True): - with vuetify.VRow( - v_for="(latticeElement, index) in selected_lattice_list", - align="center", - no_gutters=True, - style="min-width: 1500px;", - ): - with vuetify.VCol(cols="auto", classes="pa-2"): - vuetify.VIcon( - "mdi-menu-up", - click=( - ctrl.move_latticeElementIndex_up, - "[index]", - ), - ) - vuetify.VIcon( - "mdi-menu-down", - click=( - ctrl.move_latticeElementIndex_down, - "[index]", - ), - ) - vuetify.VIcon( - "mdi-delete", - click=( - ctrl.deleteLatticeElement, - "[index]", - ), - ) - vuetify.VChip( - v_text=("latticeElement.name",), - dense=True, - classes="mr-2", - style="justify-content: center", - ) - with vuetify.VCol( - v_for="(parameter, parameterIndex) in latticeElement.parameters", - cols="auto", - classes="pa-2", - ): - vuetify.VTextField( - label=("parameter.parameter_name",), - v_model=( - "parameter.parameter_default_value", - ), - change=( - ctrl.updateLatticeElementParameters, - "[index, parameter.parameter_name, $event, parameter.parameter_type]", - ), - error_messages=( - "parameter.parameter_error_message", - ), - dense=True, - style="width: 100px;", - ) + LatticeConfiguration.configuration_list() + + # ----------------------------------------------------------------------------- + # Dialogs + # ----------------------------------------------------------------------------- @staticmethod - def dialog_lattice_elementList(): + def dialog_configuration_list(): """ - Displays the content shown on the dialog - box for lattice configuration. + Displays the configuration for lattice elements + shown in a dialog box. """ with vuetify.VCard(): - with vuetify.VCardTitle("Elements", classes="text-subtitle-2 pa-3"): + with vuetify.VCardTitle("Elements", classes="headline d-flex align-center"): vuetify.VSpacer() + with vuetify.VBtn(icon=True, click="showDialog = false"): + vuetify.VIcon("mdi-close") vuetify.VDivider() - with vuetify.VContainer(fluid=True): - with vuetify.VRow( - v_for="(latticeElement, index) in selected_lattice_list", - align="center", - no_gutters=True, - style="min-width: 1500px;", - ): - with vuetify.VCol(cols="auto", classes="pa-2"): - vuetify.VIcon( - "mdi-delete", - click=(ctrl.deleteLatticeElement, "[index]"), - ) - vuetify.VChip( - v_text=("latticeElement.name",), - dense=True, - classes="mr-2", - style="justify-content: center", - ) - with vuetify.VCol( - v_for="(parameter, parameterIndex) in latticeElement.parameters", - cols="auto", - classes="pa-2", - ): - vuetify.VTextField( - label=("parameter.parameter_name",), - v_model=("parameter.parameter_default_value",), - change=( - ctrl.updateLatticeElementParameters, - "[index, parameter.parameter_name, $event, parameter.parameter_type]", - ), - error_messages=("parameter.parameter_error_message",), - dense=True, - style="width: 100px;", - ) + LatticeConfiguration.configuration_list() @staticmethod - def dialog_lattice_settings(): + def dialog_settings(): """ - Creates UI content for lattice configuration - settings. + Provides controls for lattice element configuration, + allowing dashboard users to define parameter defaults. """ + dialog_name = "lattice_configuration_dialog_tab_settings" + + TrameFunctions.create_dialog_tabs(dialog_name, 1, ["Defaults"]) + with vuetify.VTabsItems(v_model=(dialog_name, 0)): + with vuetify.VTabItem(): + with vuetify.VCardText(): + with vuetify.VRow(): + with vuetify.VCol(cols=3): + TrameFunctions.text_field( + label="nslice", + v_model_name="nslice", + change=( + ctrl.nsliceDefaultChange, + "['nslice', $event]", + ), + ) + + # ----------------------------------------------------------------------------- + # lattice_configuration_lsit + # ----------------------------------------------------------------------------- - with vuetify.VCard(): - with vuetify.VTabs(v_model=("tab", "Settings")): - vuetify.VTab("Settings") - # vuetify.VTab("Variable Referencing") - vuetify.VDivider() - with vuetify.VTabsItems(v_model="tab"): - with vuetify.VTabItem(): - with vuetify.VContainer(fluid=True): - with vuetify.VRow(no_gutters=True, align="center"): - with vuetify.VCol(no_gutters=True, cols="auto"): - vuetify.VListItem( - "nslice", classes="ma-0 pl-0 font-weight-bold" - ) - with vuetify.VCol(no_gutters=True): - vuetify.VTextField( - v_model=("nsliceDefaultValue",), - change=( - ctrl.nsliceDefaultChange, - "['nslice', $event]", - ), - type="number", - step=generalFunctions.get_default( - "nslice", "steps" - ), - __properties=["step"], - placeholder="Value", - dense=True, - outlined=True, - hide_details=True, - style="max-width: 75px", - classes="ma-0 pa-0", - ) + @staticmethod + def configuration_list(): + """ + Displays the configuration for lattice elements. + """ + with vuetify.VContainer(fluid=True): + with vuetify.VRow( + v_for="(latticeElement, index) in selected_lattice_list", + align="center", + no_gutters=True, + style="min-width: 1500px;", + ): + with vuetify.VCol(cols="auto", classes="pa-2"): + vuetify.VIcon( + "mdi-menu-up", + click=(ctrl.move_latticeElementIndex_up, "[index]"), + ) + vuetify.VIcon( + "mdi-menu-down", + click=(ctrl.move_latticeElementIndex_down, "[index]"), + ) + vuetify.VIcon( + "mdi-delete", + click=(ctrl.deleteLatticeElement, "[index]"), + ) + vuetify.VChip( + v_text=("latticeElement.name",), + dense=True, + classes="mr-2", + style="justify-content: center", + ) + with vuetify.VCol( + v_for="(parameter, parameterIndex) in latticeElement.parameters", + cols="auto", + classes="pa-2", + ): + vuetify.VTextField( + label=("parameter.parameter_name",), + v_model=("parameter.parameter_default_value",), + change=( + ctrl.updateLatticeElementParameters, + "[index, parameter.parameter_name, $event, parameter.parameter_type]", + ), + error_messages=("parameter.parameter_error_message",), + dense=True, + style="width: 100px;", + ) diff --git a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeFunctions.py b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeFunctions.py index 514978b85..1152eef2c 100644 --- a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeFunctions.py +++ b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeFunctions.py @@ -58,10 +58,10 @@ def validate_n_cell_and_blocking_factor(direction): blocking_factor_value, "int", ["non_zero", "positive"] ) - setattr(state, f"error_message_n_cell_{direction}", "; ".join(n_cell_errors)) + setattr(state, f"n_cell_{direction}_error_message", "; ".join(n_cell_errors)) setattr( state, - f"error_message_blocking_factor_{direction}", + f"blocking_factor_{direction}_error_message", "; ".join(blocking_factor_errors), ) @@ -71,6 +71,6 @@ def validate_n_cell_and_blocking_factor(direction): if n_cell_value % blocking_factor_value != 0: setattr( state, - f"error_message_n_cell_{direction}", + f"n_cell_{direction}_error_message", "Must be a multiple of blocking factor.", ) diff --git a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py index c9a920b49..33f8101ab 100644 --- a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py +++ b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py @@ -1,8 +1,4 @@ -from trame.widgets import vuetify - -from ...Input.trameFunctions import TrameFunctions -from ...trame_setup import setup_server -from ..generalFunctions import generalFunctions +from .. import TrameFunctions, generalFunctions, setup_server, vuetify from .spaceChargeFunctions import SpaceChargeFunctions server, state, ctrl = setup_server() @@ -11,42 +7,10 @@ # Default # ----------------------------------------------------------------------------- -state.dynamic_size = generalFunctions.get_default("dynamic_size", "default_values") -state.max_level = generalFunctions.get_default("max_level", "default_values") -state.particle_shape = generalFunctions.get_default("particle_shape", "default_values") -state.poisson_solver = generalFunctions.get_default("poisson_solver", "default_values") - state.prob_relative = [] state.prob_relative_fields = [] - state.n_cell = [] -state.n_cell_x = generalFunctions.get_default("n_cell_x", "default_values") -state.n_cell_y = generalFunctions.get_default("n_cell_y", "default_values") -state.n_cell_z = generalFunctions.get_default("n_cell_z", "default_values") - -state.blocking_factor_x = generalFunctions.get_default( - "blocking_factor_x", "default_values" -) -state.blocking_factor_y = generalFunctions.get_default( - "blocking_factor_y", "default_values" -) -state.blocking_factor_z = generalFunctions.get_default( - "blocking_factor_z", "default_values" -) - -state.mlmg_relative_tolerance = generalFunctions.get_default( - "mlmg_relative_tolerance", "default_values" -) -state.mlmg_absolute_tolerance = generalFunctions.get_default( - "mlmg_absolute_tolerance", "default_values" -) -state.mlmg_max_iters = generalFunctions.get_default("mlmg_max_iters", "default_values") -state.mlmg_verbosity = generalFunctions.get_default("mlmg_verbosity", "default_values") - -state.error_message_mlmg_relative_tolerance = "" -state.error_message_mlmg_absolute_tolerance = "" -state.error_message_mlmg_max_iters = "" -state.error_message_mlmg_verbosity = "" + # ----------------------------------------------------------------------------- # Helper functions @@ -93,9 +57,9 @@ def update_blocking_factor_and_n_cell(category, kwargs): direction = state_name.split("_")[-1] SpaceChargeFunctions.validate_n_cell_and_blocking_factor(direction) - n_cell_error = getattr(state, f"error_message_n_cell_{direction}") + n_cell_error = getattr(state, f"n_cell_{direction}_error_message") blocking_factor_error = getattr( - state, f"error_message_blocking_factor_{direction}" + state, f"blocking_factor_{direction}_error_message" ) if not n_cell_error: @@ -185,6 +149,14 @@ def on_update_prob_relative_call(index, value): # ----------------------------------------------------------------------------- +def multigrid_settings(): + vuetify.VIcon( + "mdi-cog", + v_if="poisson_solver == 'multigrid'", + click="space_charge_dialog_settings = true", + ) + + class SpaceChargeConfiguration: @staticmethod def card(): @@ -192,66 +164,29 @@ def card(): Creates UI content for space charge configuration """ - with vuetify.VDialog(v_model=("showSpaceChargeDialog", False), width="500px"): - SpaceChargeConfiguration.dialog_space_charge_settings() + with vuetify.VDialog( + v_model=("space_charge_dialog_settings", False), width="500px" + ): + SpaceChargeConfiguration.dialog_settings() with vuetify.VCard(v_show="space_charge", style="width: 340px;"): - with vuetify.VCardTitle("Space Charge"): - vuetify.VSpacer() - vuetify.VIcon( - "mdi-cog", - classes="ml-2", - v_if="poisson_solver == 'multigrid'", - click="showSpaceChargeDialog = true", - style="cursor: pointer;", - ) - TrameFunctions.create_refresh_button( - lambda: generalFunctions.reset_inputs("space_charge") - ) - vuetify.VIcon( - "mdi-information", - classes="ml-2", - click=lambda: generalFunctions.documentation( - "space_charge_documentation" - ), - style="color: #00313C;", - ) - vuetify.VDivider() + TrameFunctions.input_section_header( + "Space Charge", additional_components=multigrid_settings + ) with vuetify.VCardText(): with vuetify.VRow(classes="my-0"): with vuetify.VCol(cols=5, classes="py-0"): - vuetify.VSelect( + TrameFunctions.select( label="Poisson Solver", - v_model=("poisson_solver",), - items=( - generalFunctions.get_default( - "poisson_solver_list", "default_values" - ), - ), - dense=True, hide_details=True, ) with vuetify.VCol(cols=4, classes="py-0"): - vuetify.VSelect( + TrameFunctions.select( label="Particle Shape", - v_model=("particle_shape",), - items=( - generalFunctions.get_default( - "particle_shape_list", "default_values" - ), - ), - dense=True, ) with vuetify.VCol(cols=3, classes="py-0"): - vuetify.VSelect( + TrameFunctions.select( label="Max Level", - v_model=("max_level",), - items=( - generalFunctions.get_default( - "max_level_list", "default_values" - ), - ), - dense=True, ) with vuetify.VCol(classes="pa-0"): vuetify.VListItemSubtitle( @@ -261,14 +196,10 @@ def card(): with vuetify.VRow(classes="my-0"): for direction in ["x", "y", "z"]: with vuetify.VCol(cols=4, classes="py-0"): - vuetify.VTextField( - placeholder=direction, - v_model=(f"n_cell_{direction}",), - error_messages=(f"error_message_n_cell_{direction}",), - type="number", - step=generalFunctions.get_default("n_cell", "steps"), - __properties=["step"], - dense=True, + TrameFunctions.text_field( + label="", + v_model_name=f"n_cell_{direction}", + prefix=f"{direction}:", style="margin-top: -5px", ) with vuetify.VCol(classes="pa-0"): @@ -279,18 +210,10 @@ def card(): with vuetify.VRow(classes="my-0"): for direction in ["x", "y", "z"]: with vuetify.VCol(cols=4, classes="py-0"): - vuetify.VTextField( - placeholder=direction, - v_model=(f"blocking_factor_{direction}",), - error_messages=( - f"error_message_blocking_factor_{direction}", - ), - type="number", - step=generalFunctions.get_default( - "blocking_factor", "steps" - ), - __properties=["step"], - dense=True, + TrameFunctions.text_field( + label="", + prefix=f"{direction}:", + v_model_name=f"blocking_factor_{direction}", style="margin-top: -5px", ) with vuetify.VCol(classes="pa-0"): @@ -316,78 +239,33 @@ def card(): ) @staticmethod - def dialog_space_charge_settings(): + def dialog_settings(): """ Creates UI content for space charge configuration settings. """ - with vuetify.VCard(): - with vuetify.VTabs( - v_model=("space_charge_tab", "Advanced Multigrid Settings") - ): - vuetify.VTab("Settings") - vuetify.VDivider() - with vuetify.VTabsItems(v_model="space_charge_tab"): - with vuetify.VTabItem(): - with vuetify.VContainer(fluid=True): - with vuetify.VRow( - classes="my-2", v_if="poisson_solver == 'multigrid'" - ): - with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( - label="MLMG Relative Tolerance", - v_model=("mlmg_relative_tolerance",), - error_messages=( - "error_message_mlmg_relative_tolerance", - ), - type="number", - step=generalFunctions.get_default( - "mlmg_relative_tolerance", "steps" - ), - __properties=["step"], - dense=True, - ) - with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( - label="MLMG Absolute Tolerance", - v_model=("mlmg_absolute_tolerance",), - error_messages=( - "error_message_mlmg_absolute_tolerance", - ), - suffix=generalFunctions.get_default( - "mlmg_absolute_tolerance", "units" - ), - type="number", - step=generalFunctions.get_default( - "mlmg_absolute_tolerance", "steps" - ), - __properties=["step"], - dense=True, - ) - with vuetify.VRow( - classes="my-0", v_if="poisson_solver == 'multigrid'" - ): - with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( - label="MLMG Max Iterations", - v_model=("mlmg_max_iters",), - error_messages=("error_message_mlmg_max_iters",), - type="number", - step=generalFunctions.get_default( - "mlmg_max_iters", "steps" - ), - __properties=["step"], - dense=True, - ) - with vuetify.VCol(cols=6, classes="py-0"): - vuetify.VTextField( - label="MLMG Verbosity", - v_model=("mlmg_verbosity",), - error_messages=("error_message_mlmg_verbosity",), - type="number", - step=generalFunctions.get_default( - "mlmg_verbosity", "steps" - ), - __properties=["step"], - dense=True, - ) + dialog_name = "space_charge_dialog_tab_settings" + TrameFunctions.create_dialog_tabs( + dialog_name, 1, ["Advanced Multigrid Settings"] + ) + with vuetify.VTabsItems(v_model=("dialog_name", 0)): + with vuetify.VTabItem(): + with vuetify.VCardText(): + with vuetify.VRow(): + with vuetify.VCol(): + TrameFunctions.text_field( + label="MLMG Relative Tolerance", + ) + with vuetify.VCol(): + TrameFunctions.text_field( + label="MLMG Absolute Tolerance", + ) + with vuetify.VRow(): + with vuetify.VCol(): + TrameFunctions.text_field( + label="MLMG Max Iters", + ) + with vuetify.VCol(): + TrameFunctions.text_field( + label="MLMG Verbosity", + ) diff --git a/src/python/impactx/dashboard/Input/trameFunctions.py b/src/python/impactx/dashboard/Input/trameFunctions.py index ef9c58246..d540fa7f3 100644 --- a/src/python/impactx/dashboard/Input/trameFunctions.py +++ b/src/python/impactx/dashboard/Input/trameFunctions.py @@ -8,6 +8,7 @@ from trame.widgets import vuetify +from ..Input.generalFunctions import generalFunctions from ..trame_setup import setup_server server, state, ctrl = setup_server() @@ -43,13 +44,79 @@ def create_route(route_title, mdi_icon): vuetify.VListItemTitle(route_title) @staticmethod - def create_refresh_button(reset_function_name): + def select(label, v_model_name=None, items=None, **kwargs): + # in place for now as some variables are not in same format + if v_model_name is None: + v_model_name = label.lower().replace(" ", "_") + + if items is None: + items = ( + generalFunctions.get_default(f"{v_model_name}_list", "default_values"), + ) + + vuetify.VSelect( + label=label, + v_model=(f"{v_model_name}",), + items=items, + dense=True, + **kwargs, + ) + + @staticmethod + def text_field(label, v_model_name=None, **kwargs): + if v_model_name is None: + v_model_name = label.lower().replace(" ", "_") + + vuetify.VTextField( + label=label, + v_model=(f"{v_model_name}",), + error_messages=(f"{v_model_name}_error_message", []), + type="number", + step=generalFunctions.get_default(f"{v_model_name}", "steps"), + suffix=generalFunctions.get_default(f"{v_model_name}", "units"), + __properties=["step"], + dense=True, + **kwargs, + ) + + @staticmethod + def documentation_icon(section_name, **kwargs): + vuetify.VIcon( + "mdi-information", + style="color: #00313C;", + click=lambda: generalFunctions.documentation(section_name), + ) + + @staticmethod + def refresh_icon(section_name): """ Creates a standardized refresh button. :param reset_function: The reset function to call when clicked. """ - return vuetify.VIcon( + vuetify.VIcon( "mdi-refresh", style="color: #00313C;", - click=reset_function_name, + click=lambda: generalFunctions.reset_inputs(section_name), ) + + @staticmethod + def input_section_header(section_name, additional_components=None): + documentation_name = section_name.lower().replace(" ", "_") + with vuetify.VCardTitle(section_name): + vuetify.VSpacer() + if additional_components: + additional_components() + TrameFunctions.refresh_icon(documentation_name) + TrameFunctions.documentation_icon(documentation_name) + vuetify.VDivider() + + @staticmethod + def create_dialog_tabs(name: str, num_tabs: int, tab_names: list[str]): + if len(tab_names) != num_tabs: + raise ValueError("Number of tab names must match number of tabs_names") + + with vuetify.VCard(): + with vuetify.VTabs(v_model=(f"{name}", 0)): + for tab_name in tab_names: + vuetify.VTab(tab_name) + vuetify.VDivider() diff --git a/src/python/impactx/dashboard/Toolbar/toolbarMain.py b/src/python/impactx/dashboard/Toolbar/toolbarMain.py index 810a3ac62..c3288510e 100644 --- a/src/python/impactx/dashboard/Toolbar/toolbarMain.py +++ b/src/python/impactx/dashboard/Toolbar/toolbarMain.py @@ -96,36 +96,22 @@ def dashboard_info(): class Toolbars: """ - Builds section toolbars for various pages. + Builds toolbar for dashboard. """ @staticmethod - def input_toolbar(): - """ - Builds toolbar for the 'Input' page. - """ - - (ToolbarElements.dashboard_info(),) - vuetify.VSpacer() - ToolbarElements.reset_inputs_button() - ToolbarElements.export_input_data() - - @staticmethod - def run_toolbar(): - """ - Builds toolbar for the 'Run' page. - """ - - (ToolbarElements.dashboard_info(),) - (vuetify.VSpacer(),) - (ToolbarElements.run_simulation_button(),) - - @staticmethod - def analyze_toolbar(): - """ - Builds toolbar for the 'Analyze' page. - """ - - (ToolbarElements.dashboard_info(),) - vuetify.VSpacer() - ToolbarElements.plot_options() + def dashboard_toolbar(toolbar_name: str) -> None: + toolbar_name = toolbar_name.lower() + if toolbar_name == "input": + (ToolbarElements.dashboard_info(),) + vuetify.VSpacer() + ToolbarElements.reset_inputs_button() + ToolbarElements.export_input_data() + elif toolbar_name == "run": + (ToolbarElements.dashboard_info(),) + (vuetify.VSpacer(),) + (ToolbarElements.run_simulation_button(),) + elif toolbar_name == "analyze": + (ToolbarElements.dashboard_info(),) + vuetify.VSpacer() + ToolbarElements.plot_options() diff --git a/src/python/impactx/dashboard/__main__.py b/src/python/impactx/dashboard/__main__.py index 4f15fa468..dd973d6fd 100644 --- a/src/python/impactx/dashboard/__main__.py +++ b/src/python/impactx/dashboard/__main__.py @@ -72,11 +72,11 @@ def application(): layout.title.hide() with layout.toolbar: with vuetify.Template(v_if="$route.path == '/Analyze'"): - Toolbars.analyze_toolbar() + Toolbars.dashboard_toolbar("analyze") with vuetify.Template(v_if="$route.path == '/Input'"): - Toolbars.input_toolbar() + Toolbars.dashboard_toolbar("input") with vuetify.Template(v_if="$route.path == '/Run'"): - Toolbars.run_toolbar() + Toolbars.dashboard_toolbar("run") with layout.drawer as drawer: drawer.width = 200 diff --git a/src/python/impactx/dashboard/start.py b/src/python/impactx/dashboard/start.py index 54740cd9f..2d541a53b 100644 --- a/src/python/impactx/dashboard/start.py +++ b/src/python/impactx/dashboard/start.py @@ -6,6 +6,7 @@ License: BSD-3-Clause-LBNL """ +from .Input.defaults import DashboardDefaults from .trame_setup import setup_server server, state, ctrl = setup_server() @@ -16,9 +17,20 @@ # ----------------------------------------------------------------------------- +def initialize_states(): + """ + Initializes all dashboard state values upon call. + + The issue as of now is it initialize all at once instead of by section. + """ + for name, value in DashboardDefaults.DEFAULT_VALUES.items(): + setattr(state, name, value) + + def main(): """ Launches Trame application server """ + initialize_states() server.start() return 0