From ad13d6ee9c245d3f82a6e5f5940ddcf6baf0b38a Mon Sep 17 00:00:00 2001 From: michaellans Date: Thu, 16 Jan 2025 16:24:11 -0800 Subject: [PATCH 01/18] Added load_template_yaml and set_options_from_template, gui button and combobox to call template loading --- .../gui/default/components/routine_page.py | 172 +++++++++++++++++- 1 file changed, 167 insertions(+), 5 deletions(-) diff --git a/src/badger/gui/default/components/routine_page.py b/src/badger/gui/default/components/routine_page.py index bcc8571a..6b333b8d 100644 --- a/src/badger/gui/default/components/routine_page.py +++ b/src/badger/gui/default/components/routine_page.py @@ -10,7 +10,7 @@ import numpy as np import pandas as pd from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QGroupBox, QLineEdit, QLabel, QPushButton +from PyQt5.QtWidgets import QGroupBox, QLineEdit, QLabel, QPushButton, QComboBox, QFileDialog from PyQt5.QtWidgets import QListWidgetItem, QMessageBox, QWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit, QSizePolicy @@ -51,6 +51,7 @@ strtobool, get_badger_version, get_xopt_version, + get_datadir, ) CONS_RELATION_DICT = { @@ -153,6 +154,41 @@ def init_ui(self): hbox_descr.addWidget(edit_descr_col) vbox_meta.addWidget(descr) + # Templates + + # There's probable a better way to do this -- need to make sure /templates dir exists + self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/plugins/templates/" + + # Load Template ComboBox + template = QWidget() + hbox_name = QHBoxLayout(template) + hbox_name.setContentsMargins(0, 0, 0, 0) + label = QLabel("Template") + label.setFixedWidth(64) + self.template_dropdown = template_dropdown = QComboBox() + try: + files = os.listdir(self.TEMPLATE_ROOT) + except FileNotFoundError: + files = [] + files = [f for f in files if f.endswith(".yaml")] + template_dropdown.addItems(files) + hbox_name.addWidget(label) + hbox_name.addWidget(template_dropdown, 1) + vbox_meta.addWidget(template, alignment=Qt.AlignBottom) + template.show() + + # --or-- + + # Load Template Button + template_button = QWidget() + template_button.setFixedWidth(128) + hbox_name = QHBoxLayout(template_button) + hbox_name.setContentsMargins(0, 0, 0, 0) + self.load_template_button = load_template_button = QPushButton("Load Template") + hbox_name.addWidget(load_template_button, 0) + vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) + template_button.hide() + # Tags self.cbox_tags = cbox_tags = BadgerFilterBox(title=" Tags") if not strtobool(config_singleton.read_value("BADGER_ENABLE_ADVANCED")): @@ -185,6 +221,8 @@ def init_ui(self): def config_logic(self): self.btn_descr_update.clicked.connect(self.update_description) + self.template_dropdown.activated.connect(self.load_template_yaml) + self.load_template_button.clicked.connect(self.load_template_yaml) self.generator_box.cb.currentIndexChanged.connect(self.select_generator) self.generator_box.btn_docs.clicked.connect(self.open_generator_docs) self.generator_box.check_use_script.stateChanged.connect(self.toggle_use_script) @@ -208,6 +246,130 @@ def config_logic(self): self.env_box.var_table.sig_sel_changed.connect(self.update_init_table) self.env_box.var_table.sig_pv_added.connect(self.handle_pv_added) + def load_template_yaml(self): + """ + Load data from template .yaml into template_dict dictionary. + This function expects to be called via an action from either + a QPushButton, or QComboBox with filenames as menu options + """ + + # load template from button + if isinstance(self.sender(), QPushButton): + options = QFileDialog.Options() + template_path, _ = QFileDialog.getOpenFileName( + self, + "Select YAML File", + self.TEMPLATE_ROOT, + "YAML Files (*.yaml);;All Files (*)", + options=options, + ) + + if not template_path: + # file not found? Should this go somewhere else? + return + + # load template from combobox + if isinstance(self.sender(), QComboBox): + template_filename = self.template_dropdown.currentText() + if isinstance(template_filename, str) and not template_filename.endswith(".yaml"): + template_filename += ".yaml" + template_path = os.path.join( + self.TEMPLATE_ROOT, template_filename + ) + + # Load template file + try: + with open(template_path, "r") as stream: + template_dict = yaml.safe_load(stream) + self.set_options_from_template(template_dict=template_dict) + except (FileNotFoundError, yaml.YAMLError): + template_dict = {} + # throw error indicating unable to load template + return + + + def set_options_from_template(self, template_dict: dict): + """ + Fills in routine_page GUI with relevant info from template_dict + dictionary + """ + + # Compose the template + # should add some sort of check in case template_dict does not have specified key + name = template_dict["name"] + description = template_dict["description"] + relative_to_current = template_dict["relative_to_current"] + generator_name = template_dict["generator"]["name"] + env_name = template_dict["environment"]["name"] + + # set vocs + vocs = VOCS( + variables=template_dict["vocs"]["variables"], + objectives=template_dict["vocs"]["objectives"], + constraints=template_dict["vocs"]["constraints"], + constants={}, + observables=template_dict["vocs"]["observables"], + ) + + # set description + self.edit_descr.setPlainText(description) + + # set generator + if generator_name in self.generators: + i = self.generators.index(generator_name) + self.generator_box.cb.setCurrentIndex(i) + + # set environment + if env_name in self.envs: + i = self.envs.index(env_name) + self.env_box.cb.setCurrentIndex(i) + + # set init points based on relative to current + if template_dict["relative_to_current"]: + # make sure gui checkbox state matches yaml option + if not self.env_box.relative_to_curr.isChecked(): + self.env_box.relative_to_curr.setChecked(True) + + self.ratio_var_ranges = template_dict["vrange_limit_options"] + self.init_table_actions = template_dict["initial_point_actions"] # should be type: add_curr + + # set bounds (should this be somewhere else?) + bounds = self.calc_auto_bounds() + self.env_box.var_table.set_bounds(bounds) + + # set initial points to sample + self._fill_init_table() + else: + if self.env_box.relative_to_curr.isChecked(): + self.env_box.relative_to_curr.setChecked(False) + self.vrange_limit_options = None + self.initial_point_actions = None + + # set selected variables + self.env_box.var_table.set_selected(vocs.variables) + self.env_box.var_table.set_bounds(vocs.variables) + self.env_box.check_only_var.setChecked(True) + + # set objectives + self.env_box.obj_table.set_selected(vocs.objectives) + self.env_box.obj_table.set_rules(vocs.objectives) + self.env_box.check_only_obj.setChecked(True) + + # set constraints + constraints = vocs.constraints + if len(constraints): + for name, val in constraints.items(): + relation, thres = val + critical = name in template_dict["critical_constraint_names"] + relation = ["GREATER_THAN", "LESS_THAN", "EQUAL_TO"].index(relation) + self.add_constraint(name, relation, thres, critical) + + # set observables + observables = vocs.observable_names + if len(observables): + for name_sta in observables: + self.add_state(name_sta) + def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference @@ -266,12 +428,12 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): name_generator, routine.generator.model_dump() ) self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) - self.script = routine.script + self.script = routine.script #! name_env = routine.environment.name idx_env = self.envs.index(name_env) self.env_box.cb.setCurrentIndex(idx_env) - env_params = routine.environment.model_dump() + env_params = routine.environment.model_dump() #?? del env_params["interface"] self.env_box.edit.setPlainText(get_yaml_string(env_params)) @@ -301,7 +463,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): self.env_box.var_table.update_variables(all_variables) self.env_box.var_table.set_selected(variables) - self.env_box.var_table.addtl_vars = routine.additional_variables + self.env_box.var_table.addtl_vars = routine.additional_variables # ?? flag_relative = routine.relative_to_current if flag_relative: # load the relative to current settings @@ -343,7 +505,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): # Config the metadata self.edit_save.setPlaceholderText(generate_slug(2)) - self.edit_save.setText(routine.name) + self.edit_save.setText(routine.name) # ! self.edit_descr.setPlainText(routine.description) self.generator_box.check_use_script.setChecked(not not self.script) From b754df8c14aaebc8019e18e27fb0b0400c3962a6 Mon Sep 17 00:00:00 2001 From: michaellans Date: Mon, 20 Jan 2025 08:34:06 -0800 Subject: [PATCH 02/18] consolidated setting variables from template_dict.py --- .../gui/default/components/routine_page.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/badger/gui/default/components/routine_page.py b/src/badger/gui/default/components/routine_page.py index 6b333b8d..1d9ef765 100644 --- a/src/badger/gui/default/components/routine_page.py +++ b/src/badger/gui/default/components/routine_page.py @@ -158,6 +158,8 @@ def init_ui(self): # There's probable a better way to do this -- need to make sure /templates dir exists self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/plugins/templates/" + #if not os.path.exists(self.TEMPLATE_ROOT): + # os.makedirs(self.TEMPLATE_ROOT) # Load Template ComboBox template = QWidget() @@ -301,6 +303,9 @@ def set_options_from_template(self, template_dict: dict): relative_to_current = template_dict["relative_to_current"] generator_name = template_dict["generator"]["name"] env_name = template_dict["environment"]["name"] + vrange_limit_options = template_dict["vrange_limit_options"] + initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr + critical_constraint_names = template_dict["critical_constraint_names"] # set vocs vocs = VOCS( @@ -325,13 +330,13 @@ def set_options_from_template(self, template_dict: dict): self.env_box.cb.setCurrentIndex(i) # set init points based on relative to current - if template_dict["relative_to_current"]: + if relative_to_current: # make sure gui checkbox state matches yaml option if not self.env_box.relative_to_curr.isChecked(): self.env_box.relative_to_curr.setChecked(True) - self.ratio_var_ranges = template_dict["vrange_limit_options"] - self.init_table_actions = template_dict["initial_point_actions"] # should be type: add_curr + self.ratio_var_ranges = vrange_limit_options + self.init_table_actions = initial_point_actions # set bounds (should this be somewhere else?) bounds = self.calc_auto_bounds() @@ -360,7 +365,7 @@ def set_options_from_template(self, template_dict: dict): if len(constraints): for name, val in constraints.items(): relation, thres = val - critical = name in template_dict["critical_constraint_names"] + critical = name in critical_constraint_names relation = ["GREATER_THAN", "LESS_THAN", "EQUAL_TO"].index(relation) self.add_constraint(name, relation, thres, critical) @@ -433,7 +438,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): name_env = routine.environment.name idx_env = self.envs.index(name_env) self.env_box.cb.setCurrentIndex(idx_env) - env_params = routine.environment.model_dump() #?? + env_params = routine.environment.model_dump() del env_params["interface"] self.env_box.edit.setPlainText(get_yaml_string(env_params)) @@ -505,7 +510,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): # Config the metadata self.edit_save.setPlaceholderText(generate_slug(2)) - self.edit_save.setText(routine.name) # ! + self.edit_save.setText(routine.name) self.edit_descr.setPlainText(routine.description) self.generator_box.check_use_script.setChecked(not not self.script) From d3493d28ea38c57d6b05a342f43211a056d0ae6e Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 22 Jan 2025 08:46:26 -0800 Subject: [PATCH 03/18] set_options_from_template clears objectives/constraints, improved error handling in load_template_yaml() --- .../gui/default/components/routine_page.py | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/badger/gui/default/components/routine_page.py b/src/badger/gui/default/components/routine_page.py index 1d9ef765..7cc6b33a 100644 --- a/src/badger/gui/default/components/routine_page.py +++ b/src/badger/gui/default/components/routine_page.py @@ -158,8 +158,6 @@ def init_ui(self): # There's probable a better way to do this -- need to make sure /templates dir exists self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/plugins/templates/" - #if not os.path.exists(self.TEMPLATE_ROOT): - # os.makedirs(self.TEMPLATE_ROOT) # Load Template ComboBox template = QWidget() @@ -177,7 +175,7 @@ def init_ui(self): hbox_name.addWidget(label) hbox_name.addWidget(template_dropdown, 1) vbox_meta.addWidget(template, alignment=Qt.AlignBottom) - template.show() + template.hide() # --or-- @@ -189,7 +187,7 @@ def init_ui(self): self.load_template_button = load_template_button = QPushButton("Load Template") hbox_name.addWidget(load_template_button, 0) vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) - template_button.hide() + template_button.show() # Tags self.cbox_tags = cbox_tags = BadgerFilterBox(title=" Tags") @@ -248,15 +246,15 @@ def config_logic(self): self.env_box.var_table.sig_sel_changed.connect(self.update_init_table) self.env_box.var_table.sig_pv_added.connect(self.handle_pv_added) - def load_template_yaml(self): + def load_template_yaml(self) -> None: """ Load data from template .yaml into template_dict dictionary. This function expects to be called via an action from either a QPushButton, or QComboBox with filenames as menu options """ - # load template from button if isinstance(self.sender(), QPushButton): + # load template from button options = QFileDialog.Options() template_path, _ = QFileDialog.getOpenFileName( self, @@ -265,31 +263,27 @@ def load_template_yaml(self): "YAML Files (*.yaml);;All Files (*)", options=options, ) - - if not template_path: - # file not found? Should this go somewhere else? - return - - # load template from combobox - if isinstance(self.sender(), QComboBox): + elif isinstance(self.sender(), QComboBox): + # load template from combobox template_filename = self.template_dropdown.currentText() if isinstance(template_filename, str) and not template_filename.endswith(".yaml"): template_filename += ".yaml" template_path = os.path.join( self.TEMPLATE_ROOT, template_filename ) + + if not template_path: + return # Load template file try: with open(template_path, "r") as stream: template_dict = yaml.safe_load(stream) self.set_options_from_template(template_dict=template_dict) - except (FileNotFoundError, yaml.YAMLError): - template_dict = {} - # throw error indicating unable to load template + except (FileNotFoundError, yaml.YAMLError) as e: + print(f"Error loading template: {e}") return - def set_options_from_template(self, template_dict: dict): """ Fills in routine_page GUI with relevant info from template_dict @@ -368,12 +362,16 @@ def set_options_from_template(self, template_dict: dict): critical = name in critical_constraint_names relation = ["GREATER_THAN", "LESS_THAN", "EQUAL_TO"].index(relation) self.add_constraint(name, relation, thres, critical) + else: + self.env_box.list_con.clear() # set observables observables = vocs.observable_names if len(observables): for name_sta in observables: self.add_state(name_sta) + else: + self.env_box.list_obs.clear() def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference From b03d7be1f991bae7c56d6bdf1371aaea2764765a Mon Sep 17 00:00:00 2001 From: mlans Date: Fri, 24 Jan 2025 15:29:41 -0800 Subject: [PATCH 04/18] minor changes to fix bugs from testing in acr --- .../gui/default/components/routine_page.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/badger/gui/default/components/routine_page.py b/src/badger/gui/default/components/routine_page.py index 7cc6b33a..cccb6dd0 100644 --- a/src/badger/gui/default/components/routine_page.py +++ b/src/badger/gui/default/components/routine_page.py @@ -157,7 +157,9 @@ def init_ui(self): # Templates # There's probable a better way to do this -- need to make sure /templates dir exists - self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/plugins/templates/" + # self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/src/badger/built_in_pluigins/templates" + self.TEMPLATE_ROOT = "./src/badger/built_in_plugins/templates/" + # print(str(get_datadir())) # Load Template ComboBox template = QWidget() @@ -300,7 +302,8 @@ def set_options_from_template(self, template_dict: dict): vrange_limit_options = template_dict["vrange_limit_options"] initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr critical_constraint_names = template_dict["critical_constraint_names"] - + env_params = template_dict["environment"]["params"] + # set vocs vocs = VOCS( variables=template_dict["vocs"]["variables"], @@ -318,10 +321,14 @@ def set_options_from_template(self, template_dict: dict): i = self.generators.index(generator_name) self.generator_box.cb.setCurrentIndex(i) + filtered_config = filter_generator_config(generator_name, template_dict["generator"]) + self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) + # set environment if env_name in self.envs: i = self.envs.index(env_name) self.env_box.cb.setCurrentIndex(i) + self.env_box.edit.setPlainText(get_yaml_string(env_params)) # set init points based on relative to current if relative_to_current: @@ -331,22 +338,17 @@ def set_options_from_template(self, template_dict: dict): self.ratio_var_ranges = vrange_limit_options self.init_table_actions = initial_point_actions - + # set bounds (should this be somewhere else?) bounds = self.calc_auto_bounds() self.env_box.var_table.set_bounds(bounds) # set initial points to sample self._fill_init_table() - else: - if self.env_box.relative_to_curr.isChecked(): - self.env_box.relative_to_curr.setChecked(False) - self.vrange_limit_options = None - self.initial_point_actions = None # set selected variables self.env_box.var_table.set_selected(vocs.variables) - self.env_box.var_table.set_bounds(vocs.variables) + #self.env_box.var_table.set_bounds(vocs.variables) self.env_box.check_only_var.setChecked(True) # set objectives @@ -930,7 +932,7 @@ def calc_auto_bounds(self): env = self.create_env() var_curr = env._get_variables(vname_selected) - + for name in vname_selected: try: limit_option = self.ratio_var_ranges[name] From 9d47778a9f1ac179ded114b3e5a12bdd2a5c885e Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 12 Feb 2025 00:58:57 -0800 Subject: [PATCH 05/18] Added load template button to env_cbox --- src/badger/gui/acr/components/env_cbox.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/badger/gui/acr/components/env_cbox.py b/src/badger/gui/acr/components/env_cbox.py index 6e88544e..ca5d6c37 100644 --- a/src/badger/gui/acr/components/env_cbox.py +++ b/src/badger/gui/acr/components/env_cbox.py @@ -98,6 +98,16 @@ def init_ui(self): vbox = QVBoxLayout(self) vbox.setContentsMargins(8, 8, 8, 8) + # Load Template Button + template_button = QWidget() + template_button.setFixedWidth(128) + hbox_name = QHBoxLayout(template_button) + hbox_name.setContentsMargins(0, 0, 0, 0) + self.load_template_button = load_template_button = QPushButton("Load Template") + hbox_name.addWidget(load_template_button, 0) + vbox.addWidget(template_button) + template_button.show() + self.setObjectName("EnvBox") name = QWidget() From ca9fff181133c54f7a902cb9fe788005db2b4aaf Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 12 Feb 2025 00:59:51 -0800 Subject: [PATCH 06/18] Added load_template_yaml and set_options_from_template --- src/badger/gui/acr/components/routine_page.py | 140 +++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 44f9c116..929479c4 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -8,7 +8,7 @@ import numpy as np import pandas as pd from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QLineEdit, QLabel, QPushButton +from PyQt5.QtWidgets import QLineEdit, QLabel, QPushButton, QFileDialog from PyQt5.QtWidgets import QListWidgetItem, QMessageBox, QWidget, QTabWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QScrollArea from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit @@ -158,6 +158,20 @@ def init_ui(self): vbox_meta.addWidget(descr) descr_bar.hide() + # Templates + # There's probable a better way to do this + self.TEMPLATE_ROOT = "./src/badger/built_in_plugins/templates/" + + # Load Template Button + template_button = QWidget() + template_button.setFixedWidth(128) + hbox_name = QHBoxLayout(template_button) + hbox_name.setContentsMargins(0, 0, 0, 0) + self.load_template_button = load_template_button = QPushButton("Load Template") + hbox_name.addWidget(load_template_button, 0) + vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) + template_button.show() + # Tags self.cbox_tags = cbox_tags = BadgerFilterBox(title=" Tags") if not strtobool(config_singleton.read_value("BADGER_ENABLE_ADVANCED")): @@ -168,7 +182,7 @@ def init_ui(self): # vbox.addWidget(group_meta) # Env box - BADGER_PLUGIN_ROOT = config_singleton.read_value("BADGER_PLUGIN_ROOT") + self.BADGER_PLUGIN_ROOT = BADGER_PLUGIN_ROOT = config_singleton.read_value("BADGER_PLUGIN_ROOT") env_dict_dir = os.path.join( BADGER_PLUGIN_ROOT, "environments", "env_colors.yaml" ) @@ -208,6 +222,7 @@ def init_ui(self): def config_logic(self): self.btn_descr_update.clicked.connect(self.update_description) + self.env_box.load_template_button.clicked.connect(self.load_template_yaml) self.generator_box.cb.currentIndexChanged.connect(self.select_generator) self.generator_box.btn_docs.clicked.connect(self.open_generator_docs) self.generator_box.check_use_script.stateChanged.connect(self.toggle_use_script) @@ -232,6 +247,127 @@ def config_logic(self): self.env_box.var_table.sig_sel_changed.connect(self.update_init_table) self.env_box.var_table.sig_pv_added.connect(self.handle_pv_added) + def load_template_yaml(self) -> None: + """ + Load data from template .yaml into template_dict dictionary. + This function expects to be called via an action from either + a QPushButton, or QComboBox with filenames as menu options + """ + + template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + + if isinstance(self.sender(), QPushButton): + # load template from button + options = QFileDialog.Options() + template_path, _ = QFileDialog.getOpenFileName( + self, + "Select YAML File", + template_dir, + "YAML Files (*.yaml);;All Files (*)", + options=options, + ) + + if not template_path: + return + + # Load template file + try: + with open(template_path, "r") as stream: + template_dict = yaml.safe_load(stream) + self.set_options_from_template(template_dict=template_dict) + except (FileNotFoundError, yaml.YAMLError) as e: + print(f"Error loading template: {e}") + return + + def set_options_from_template(self, template_dict: dict): + """ + Fills in routine_page GUI with relevant info from template_dict + dictionary + """ + + # Compose the template + # should add some sort of check in case template_dict does not have specified key + name = template_dict["name"] + description = template_dict["description"] + relative_to_current = template_dict["relative_to_current"] + generator_name = template_dict["generator"]["name"] + env_name = template_dict["environment"]["name"] + vrange_limit_options = template_dict["vrange_limit_options"] + initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr + critical_constraint_names = template_dict["critical_constraint_names"] + #env_params = template_dict["environment"]["params"] + + # set vocs + vocs = VOCS( + variables=template_dict["vocs"]["variables"], + objectives=template_dict["vocs"]["objectives"], + constraints=template_dict["vocs"]["constraints"], + constants={}, + observables=template_dict["vocs"]["observables"], + ) + + # set description + self.edit_descr.setPlainText(description) + + # set generator + if generator_name in self.generators: + i = self.generators.index(generator_name) + self.generator_box.cb.setCurrentIndex(i) + + filtered_config = filter_generator_config(generator_name, template_dict["generator"]) + self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) + + # set environment + if env_name in self.envs: + i = self.envs.index(env_name) + self.env_box.cb.setCurrentIndex(i) + #self.env_box.edit.setPlainText(get_yaml_string(env_params)) + + # set init points based on relative to current + if relative_to_current: + # make sure gui checkbox state matches yaml option + if not self.env_box.relative_to_curr.isChecked(): + self.env_box.relative_to_curr.setChecked(True) + + self.ratio_var_ranges = vrange_limit_options + self.init_table_actions = initial_point_actions + + # set bounds (should this be somewhere else?) + bounds = self.calc_auto_bounds() + self.env_box.var_table.set_bounds(bounds) + + # set initial points to sample + self._fill_init_table() + + # set selected variables + self.env_box.var_table.set_selected(vocs.variables) + #self.env_box.var_table.set_bounds(vocs.variables) + self.env_box.check_only_var.setChecked(True) + + # set objectives + self.env_box.obj_table.set_selected(vocs.objectives) + self.env_box.obj_table.set_rules(vocs.objectives) + self.env_box.check_only_obj.setChecked(True) + + # set constraints + constraints = vocs.constraints + if len(constraints): + for name, val in constraints.items(): + relation, thres = val + critical = name in critical_constraint_names + relation = ["GREATER_THAN", "LESS_THAN", "EQUAL_TO"].index(relation) + self.add_constraint(name, relation, thres, critical) + else: + self.env_box.list_con.clear() + + # set observables + observables = vocs.observable_names + if len(observables): + for name_sta in observables: + self.add_state(name_sta) + else: + self.env_box.list_obs.clear() + def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference From 39cb30e51ed1e85c74a6481e9e634d6c57a4e3e7 Mon Sep 17 00:00:00 2001 From: michaellans Date: Fri, 14 Feb 2025 01:17:41 -0800 Subject: [PATCH 07/18] Added error handling to catch missing template keys --- src/badger/gui/acr/components/routine_page.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 929479c4..113b0d47 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -255,6 +255,7 @@ def load_template_yaml(self) -> None: """ template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + #template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" if isinstance(self.sender(), QPushButton): # load template from button @@ -286,16 +287,19 @@ def set_options_from_template(self, template_dict: dict): """ # Compose the template - # should add some sort of check in case template_dict does not have specified key - name = template_dict["name"] - description = template_dict["description"] - relative_to_current = template_dict["relative_to_current"] - generator_name = template_dict["generator"]["name"] - env_name = template_dict["environment"]["name"] - vrange_limit_options = template_dict["vrange_limit_options"] - initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr - critical_constraint_names = template_dict["critical_constraint_names"] - #env_params = template_dict["environment"]["params"] + try: + name = template_dict["name"] + description = template_dict["description"] + relative_to_current = template_dict["relative_to_current"] + generator_name = template_dict["generator"]["name"] + env_name = template_dict["environment"]["name"] + vrange_limit_options = template_dict["vrange_limit_options"] + initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr + critical_constraint_names = template_dict["critical_constraint_names"] + env_params = template_dict["environment"]["params"] + except KeyError as e: + QMessageBox.warning(self, "Error", f"Missing key in template: {e}") + return # set vocs vocs = VOCS( @@ -321,7 +325,7 @@ def set_options_from_template(self, template_dict: dict): if env_name in self.envs: i = self.envs.index(env_name) self.env_box.cb.setCurrentIndex(i) - #self.env_box.edit.setPlainText(get_yaml_string(env_params)) + self.env_box.edit.setPlainText(get_yaml_string(env_params)) # set init points based on relative to current if relative_to_current: @@ -1224,4 +1228,4 @@ def update_description(self): f"Routine {self.routine.name} description was updated!", ) except Exception: - return QMessageBox.critical(self, "Update failed!", traceback.format_exc()) + return QMessageBox.critical(self, "Update failed!", traceback.format_exc()) \ No newline at end of file From 3923e298d47fa9773be45214d4ce13401dcabb9a Mon Sep 17 00:00:00 2001 From: michaellans Date: Fri, 14 Feb 2025 01:36:47 -0800 Subject: [PATCH 08/18] removed unnecessary elements from template update --- src/badger/gui/acr/components/routine_page.py | 6 +- .../gui/default/components/routine_page.py | 175 +----------------- 2 files changed, 5 insertions(+), 176 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 113b0d47..e73e1eda 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -158,10 +158,6 @@ def init_ui(self): vbox_meta.addWidget(descr) descr_bar.hide() - # Templates - # There's probable a better way to do this - self.TEMPLATE_ROOT = "./src/badger/built_in_plugins/templates/" - # Load Template Button template_button = QWidget() template_button.setFixedWidth(128) @@ -1228,4 +1224,4 @@ def update_description(self): f"Routine {self.routine.name} description was updated!", ) except Exception: - return QMessageBox.critical(self, "Update failed!", traceback.format_exc()) \ No newline at end of file + return QMessageBox.critical(self, "Update failed!", traceback.format_exc()) diff --git a/src/badger/gui/default/components/routine_page.py b/src/badger/gui/default/components/routine_page.py index 583fa32b..1d27c570 100644 --- a/src/badger/gui/default/components/routine_page.py +++ b/src/badger/gui/default/components/routine_page.py @@ -10,7 +10,7 @@ import numpy as np import pandas as pd from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QGroupBox, QLineEdit, QLabel, QPushButton, QComboBox, QFileDialog +from PyQt5.QtWidgets import QGroupBox, QLineEdit, QLabel, QPushButton from PyQt5.QtWidgets import QListWidgetItem, QMessageBox, QWidget from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit, QSizePolicy @@ -51,7 +51,6 @@ strtobool, get_badger_version, get_xopt_version, - get_datadir, ) CONS_RELATION_DICT = { @@ -157,43 +156,6 @@ def init_ui(self): hbox_descr.addWidget(edit_descr_col) vbox_meta.addWidget(descr) - # Templates - - # There's probable a better way to do this -- need to make sure /templates dir exists - # self.TEMPLATE_ROOT = str(get_datadir()) + "/Badger/src/badger/built_in_pluigins/templates" - self.TEMPLATE_ROOT = "./src/badger/built_in_plugins/templates/" - # print(str(get_datadir())) - - # Load Template ComboBox - template = QWidget() - hbox_name = QHBoxLayout(template) - hbox_name.setContentsMargins(0, 0, 0, 0) - label = QLabel("Template") - label.setFixedWidth(64) - self.template_dropdown = template_dropdown = QComboBox() - try: - files = os.listdir(self.TEMPLATE_ROOT) - except FileNotFoundError: - files = [] - files = [f for f in files if f.endswith(".yaml")] - template_dropdown.addItems(files) - hbox_name.addWidget(label) - hbox_name.addWidget(template_dropdown, 1) - vbox_meta.addWidget(template, alignment=Qt.AlignBottom) - template.hide() - - # --or-- - - # Load Template Button - template_button = QWidget() - template_button.setFixedWidth(128) - hbox_name = QHBoxLayout(template_button) - hbox_name.setContentsMargins(0, 0, 0, 0) - self.load_template_button = load_template_button = QPushButton("Load Template") - hbox_name.addWidget(load_template_button, 0) - vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) - template_button.show() - # Tags self.cbox_tags = cbox_tags = BadgerFilterBox(title=" Tags") if not strtobool(config_singleton.read_value("BADGER_ENABLE_ADVANCED")): @@ -226,8 +188,6 @@ def init_ui(self): def config_logic(self): self.btn_descr_update.clicked.connect(self.update_description) - self.template_dropdown.activated.connect(self.load_template_yaml) - self.load_template_button.clicked.connect(self.load_template_yaml) self.generator_box.cb.currentIndexChanged.connect(self.select_generator) self.generator_box.btn_docs.clicked.connect(self.open_generator_docs) self.generator_box.check_use_script.stateChanged.connect(self.toggle_use_script) @@ -251,133 +211,6 @@ def config_logic(self): self.env_box.var_table.sig_sel_changed.connect(self.update_init_table) self.env_box.var_table.sig_pv_added.connect(self.handle_pv_added) - def load_template_yaml(self) -> None: - """ - Load data from template .yaml into template_dict dictionary. - This function expects to be called via an action from either - a QPushButton, or QComboBox with filenames as menu options - """ - - if isinstance(self.sender(), QPushButton): - # load template from button - options = QFileDialog.Options() - template_path, _ = QFileDialog.getOpenFileName( - self, - "Select YAML File", - self.TEMPLATE_ROOT, - "YAML Files (*.yaml);;All Files (*)", - options=options, - ) - elif isinstance(self.sender(), QComboBox): - # load template from combobox - template_filename = self.template_dropdown.currentText() - if isinstance(template_filename, str) and not template_filename.endswith(".yaml"): - template_filename += ".yaml" - template_path = os.path.join( - self.TEMPLATE_ROOT, template_filename - ) - - if not template_path: - return - - # Load template file - try: - with open(template_path, "r") as stream: - template_dict = yaml.safe_load(stream) - self.set_options_from_template(template_dict=template_dict) - except (FileNotFoundError, yaml.YAMLError) as e: - print(f"Error loading template: {e}") - return - - def set_options_from_template(self, template_dict: dict): - """ - Fills in routine_page GUI with relevant info from template_dict - dictionary - """ - - # Compose the template - # should add some sort of check in case template_dict does not have specified key - name = template_dict["name"] - description = template_dict["description"] - relative_to_current = template_dict["relative_to_current"] - generator_name = template_dict["generator"]["name"] - env_name = template_dict["environment"]["name"] - vrange_limit_options = template_dict["vrange_limit_options"] - initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr - critical_constraint_names = template_dict["critical_constraint_names"] - env_params = template_dict["environment"]["params"] - - # set vocs - vocs = VOCS( - variables=template_dict["vocs"]["variables"], - objectives=template_dict["vocs"]["objectives"], - constraints=template_dict["vocs"]["constraints"], - constants={}, - observables=template_dict["vocs"]["observables"], - ) - - # set description - self.edit_descr.setPlainText(description) - - # set generator - if generator_name in self.generators: - i = self.generators.index(generator_name) - self.generator_box.cb.setCurrentIndex(i) - - filtered_config = filter_generator_config(generator_name, template_dict["generator"]) - self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) - - # set environment - if env_name in self.envs: - i = self.envs.index(env_name) - self.env_box.cb.setCurrentIndex(i) - self.env_box.edit.setPlainText(get_yaml_string(env_params)) - - # set init points based on relative to current - if relative_to_current: - # make sure gui checkbox state matches yaml option - if not self.env_box.relative_to_curr.isChecked(): - self.env_box.relative_to_curr.setChecked(True) - - self.ratio_var_ranges = vrange_limit_options - self.init_table_actions = initial_point_actions - - # set bounds (should this be somewhere else?) - bounds = self.calc_auto_bounds() - self.env_box.var_table.set_bounds(bounds) - - # set initial points to sample - self._fill_init_table() - - # set selected variables - self.env_box.var_table.set_selected(vocs.variables) - #self.env_box.var_table.set_bounds(vocs.variables) - self.env_box.check_only_var.setChecked(True) - - # set objectives - self.env_box.obj_table.set_selected(vocs.objectives) - self.env_box.obj_table.set_rules(vocs.objectives) - self.env_box.check_only_obj.setChecked(True) - - # set constraints - constraints = vocs.constraints - if len(constraints): - for name, val in constraints.items(): - relation, thres = val - critical = name in critical_constraint_names - relation = ["GREATER_THAN", "LESS_THAN", "EQUAL_TO"].index(relation) - self.add_constraint(name, relation, thres, critical) - else: - self.env_box.list_con.clear() - - # set observables - observables = vocs.observable_names - if len(observables): - for name_sta in observables: - self.add_state(name_sta) - else: - self.env_box.list_obs.clear() - def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference @@ -436,7 +269,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): name_generator, routine.generator.model_dump() ) self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) - self.script = routine.script #! + self.script = routine.script name_env = routine.environment.name idx_env = self.envs.index(name_env) @@ -471,7 +304,7 @@ def refresh_ui(self, routine: Routine = None, silent: bool = False): self.env_box.var_table.update_variables(all_variables) self.env_box.var_table.set_selected(variables) - self.env_box.var_table.addtl_vars = routine.additional_variables # ?? + self.env_box.var_table.addtl_vars = routine.additional_variables flag_relative = routine.relative_to_current if flag_relative: # load the relative to current settings @@ -935,7 +768,7 @@ def calc_auto_bounds(self): env = self.create_env() var_curr = env._get_variables(vname_selected) - + for name in vname_selected: try: limit_option = self.ratio_var_ranges[name] From ac65fc4d2647574e73cc2e2882562d31adb6d426 Mon Sep 17 00:00:00 2001 From: michaellans Date: Sat, 15 Feb 2025 03:41:28 -0800 Subject: [PATCH 09/18] added generate_template_dict_from_gui and save_template_yaml to save current options as yaml template --- src/badger/gui/acr/components/routine_page.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index e73e1eda..97dda4ac 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -168,6 +168,16 @@ def init_ui(self): vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) template_button.show() + # Save Template Button + template_button = QWidget() + template_button.setFixedWidth(128) + hbox_name = QHBoxLayout(template_button) + hbox_name.setContentsMargins(0, 0, 0, 0) + self.save_template_button = save_template_button = QPushButton("Save Template") + hbox_name.addWidget(save_template_button, 1) + vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) + template_button.show() + # Tags self.cbox_tags = cbox_tags = BadgerFilterBox(title=" Tags") if not strtobool(config_singleton.read_value("BADGER_ENABLE_ADVANCED")): @@ -219,6 +229,7 @@ def init_ui(self): def config_logic(self): self.btn_descr_update.clicked.connect(self.update_description) self.env_box.load_template_button.clicked.connect(self.load_template_yaml) + self.save_template_button.clicked.connect(self.save_template_yaml) self.generator_box.cb.currentIndexChanged.connect(self.select_generator) self.generator_box.btn_docs.clicked.connect(self.open_generator_docs) self.generator_box.check_use_script.stateChanged.connect(self.toggle_use_script) @@ -367,6 +378,62 @@ def set_options_from_template(self, template_dict: dict): self.add_state(name_sta) else: self.env_box.list_obs.clear() + + def generate_template_dict_from_gui(self): + """ + Generate a template dictionary from the current state of the GUI + """ + + vocs, critical_constraints = self._compose_vocs() + + template_dict = { + "name": self.edit_save.text(), + "description": str(self.edit_descr.toPlainText()), + "relative_to_current": self.env_box.relative_to_curr.isChecked(), + "generator": { + "name": self.generator_box.cb.currentText(), + "config": load_config(self.generator_box.edit.toPlainText()) + }, + "environment": { + "name": self.env_box.cb.currentText(), + "params": load_config(self.env_box.edit.toPlainText()) + }, + "vrange_limit_options": self.ratio_var_ranges, + "initial_point_actions": self.init_table_actions, + "critical_constraint_names": critical_constraints, + "vocs": vars(vocs), + "badger_version": get_badger_version(), + "xopt_version": get_xopt_version(), + } + + return template_dict + + def save_template_yaml(self): + """ + Save the current routine as a template .yaml file + """ + + template_dict = self.generate_template_dict_from_gui() + + template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + options = QFileDialog.Options() + template_path, _ = QFileDialog.getSaveFileName( + self, + "Save Template", + template_dir, + "YAML Files (*.yaml);;All Files (*)", + options=options, + ) + + if not template_path: + return + + try: + with open(template_path, "w") as stream: + yaml.dump(template_dict, stream) + except (FileNotFoundError, yaml.YAMLError) as e: + print(f"Error saving template: {e}") + return def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference From 9650ac8ab99a5efa65810a34d6403bc31bd62c5c Mon Sep 17 00:00:00 2001 From: mlans Date: Sat, 15 Feb 2025 05:53:40 -0800 Subject: [PATCH 10/18] bug fixes from acr testing --- src/badger/gui/acr/components/routine_page.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 97dda4ac..d2677cf1 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -97,6 +97,10 @@ def __init__(self): # Trigger the re-rendering of the environment box self.env_box.relative_to_curr.setChecked(True) + + # Template path + #template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + self.template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" def init_ui(self): config_singleton = init_settings() @@ -173,7 +177,7 @@ def init_ui(self): template_button.setFixedWidth(128) hbox_name = QHBoxLayout(template_button) hbox_name.setContentsMargins(0, 0, 0, 0) - self.save_template_button = save_template_button = QPushButton("Save Template") + self.save_template_button = save_template_button = QPushButton("Save as Template") hbox_name.addWidget(save_template_button, 1) vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) template_button.show() @@ -261,16 +265,13 @@ def load_template_yaml(self) -> None: a QPushButton, or QComboBox with filenames as menu options """ - template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") - #template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" - if isinstance(self.sender(), QPushButton): # load template from button options = QFileDialog.Options() template_path, _ = QFileDialog.getOpenFileName( self, "Select YAML File", - template_dir, + self.template_dir, "YAML Files (*.yaml);;All Files (*)", options=options, ) @@ -344,8 +345,9 @@ def set_options_from_template(self, template_dict: dict): self.init_table_actions = initial_point_actions # set bounds (should this be somewhere else?) - bounds = self.calc_auto_bounds() - self.env_box.var_table.set_bounds(bounds) + if env_name: + bounds = self.calc_auto_bounds() + self.env_box.var_table.set_bounds(bounds) # set initial points to sample self._fill_init_table() @@ -385,6 +387,14 @@ def generate_template_dict_from_gui(self): """ vocs, critical_constraints = self._compose_vocs() + + # set bounds to variable range limits (avoids confusion when looking at template file) + for var in self.env_box.var_table.variables: + name = next(iter(var)) + if self.env_box.var_table.is_checked(name): + bounds = var[name] + if name in vocs.variables: + vocs.variables[name] = bounds template_dict = { "name": self.edit_save.text(), @@ -392,8 +402,8 @@ def generate_template_dict_from_gui(self): "relative_to_current": self.env_box.relative_to_curr.isChecked(), "generator": { "name": self.generator_box.cb.currentText(), - "config": load_config(self.generator_box.edit.toPlainText()) - }, + + } | load_config(self.generator_box.edit.toPlainText()), "environment": { "name": self.env_box.cb.currentText(), "params": load_config(self.env_box.edit.toPlainText()) @@ -415,12 +425,11 @@ def save_template_yaml(self): template_dict = self.generate_template_dict_from_gui() - template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") options = QFileDialog.Options() template_path, _ = QFileDialog.getSaveFileName( self, "Save Template", - template_dir, + self.template_dir, "YAML Files (*.yaml);;All Files (*)", options=options, ) From c23b54458e5ad92e71285d7732b0f194a88a719f Mon Sep 17 00:00:00 2001 From: michaellans Date: Sat, 15 Feb 2025 08:50:56 -0800 Subject: [PATCH 11/18] Added status bar messages for saving and loading templates --- src/badger/gui/acr/components/routine_editor.py | 4 ++++ src/badger/gui/acr/components/routine_page.py | 4 ++++ src/badger/gui/acr/pages/home_page.py | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/badger/gui/acr/components/routine_editor.py b/src/badger/gui/acr/components/routine_editor.py index 05aaf2ba..2e988c0e 100644 --- a/src/badger/gui/acr/components/routine_editor.py +++ b/src/badger/gui/acr/components/routine_editor.py @@ -11,6 +11,8 @@ class BadgerRoutineEditor(QWidget): sig_saved = pyqtSignal() sig_canceled = pyqtSignal() sig_deleted = pyqtSignal() + sig_load_template = pyqtSignal(str) + sig_save_template = pyqtSignal(str) def __init__(self): super().__init__() @@ -41,6 +43,8 @@ def init_ui(self): # scroll_area.setWidgetResizable(True) # scroll_area.setWidget(routine_page) stacks.addWidget(routine_page) + routine_page.sig_load_template.connect(self.sig_load_template.emit) + routine_page.sig_save_template.connect(self.sig_save_template.emit) stacks.setCurrentIndex(1) vbox.addWidget(stacks) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index d2677cf1..95cd5d0a 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -59,6 +59,8 @@ class BadgerRoutinePage(QWidget): sig_updated = pyqtSignal(str, str) # routine name, routine description + sig_load_template = pyqtSignal(str) # template path + sig_save_template = pyqtSignal(str) # template path def __init__(self): super().__init__() @@ -284,6 +286,7 @@ def load_template_yaml(self) -> None: with open(template_path, "r") as stream: template_dict = yaml.safe_load(stream) self.set_options_from_template(template_dict=template_dict) + self.sig_load_template.emit(f"Options loaded from template: {os.path.basename(template_path)}") except (FileNotFoundError, yaml.YAMLError) as e: print(f"Error loading template: {e}") return @@ -440,6 +443,7 @@ def save_template_yaml(self): try: with open(template_path, "w") as stream: yaml.dump(template_dict, stream) + self.sig_save_template.emit(f"Current routine options saved to template: {os.path.basename(template_path)}") except (FileNotFoundError, yaml.YAMLError) as e: print(f"Error saving template: {e}") return diff --git a/src/badger/gui/acr/pages/home_page.py b/src/badger/gui/acr/pages/home_page.py index fe7c9e06..6a17410c 100644 --- a/src/badger/gui/acr/pages/home_page.py +++ b/src/badger/gui/acr/pages/home_page.py @@ -187,6 +187,9 @@ def config_logic(self): self.history_browser.tree_widget.itemSelectionChanged.connect(self.go_run) + self.routine_editor.sig_load_template.connect(self.update_status) + self.routine_editor.sig_save_template.connect(self.update_status) + self.run_monitor.sig_inspect.connect(self.inspect_solution) self.run_monitor.sig_lock.connect(self.toggle_lock) self.run_monitor.sig_new_run.connect(self.new_run) From 8de317102d2ee7ffa2976bc3a8202b653b4fb1d8 Mon Sep 17 00:00:00 2001 From: mlans Date: Sat, 15 Feb 2025 09:09:57 -0800 Subject: [PATCH 12/18] moved save template button to right --- src/badger/gui/acr/components/routine_page.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 95cd5d0a..fd4a7692 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -164,7 +164,7 @@ def init_ui(self): vbox_meta.addWidget(descr) descr_bar.hide() - # Load Template Button + """# Load Template Button template_button = QWidget() template_button.setFixedWidth(128) hbox_name = QHBoxLayout(template_button) @@ -172,15 +172,15 @@ def init_ui(self): self.load_template_button = load_template_button = QPushButton("Load Template") hbox_name.addWidget(load_template_button, 0) vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) - template_button.show() + template_button.show()""" # Save Template Button template_button = QWidget() - template_button.setFixedWidth(128) hbox_name = QHBoxLayout(template_button) hbox_name.setContentsMargins(0, 0, 0, 0) self.save_template_button = save_template_button = QPushButton("Save as Template") - hbox_name.addWidget(save_template_button, 1) + save_template_button.setFixedWidth(128) + hbox_name.addWidget(save_template_button, alignment=Qt.AlignRight) vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) template_button.show() From 05dff3bf3b4c4c4b65e51ab4484e24b696e9247e Mon Sep 17 00:00:00 2001 From: mlans Date: Tue, 18 Feb 2025 03:30:36 -0800 Subject: [PATCH 13/18] fixed issues with saving vrange_limit_options to yaml template --- src/badger/gui/acr/components/routine_page.py | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index fd4a7692..1f0a8afb 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -343,17 +343,21 @@ def set_options_from_template(self, template_dict: dict): # make sure gui checkbox state matches yaml option if not self.env_box.relative_to_curr.isChecked(): self.env_box.relative_to_curr.setChecked(True) + + else: + if self.env_box.relative_to_curr.isChecked(): + self.env_box.relative_to_curr.setChecked(False) - self.ratio_var_ranges = vrange_limit_options - self.init_table_actions = initial_point_actions - - # set bounds (should this be somewhere else?) - if env_name: - bounds = self.calc_auto_bounds() - self.env_box.var_table.set_bounds(bounds) + self.ratio_var_ranges = vrange_limit_options + self.init_table_actions = initial_point_actions + + # set bounds (should this be somewhere else?) + if env_name: + bounds = self.calc_auto_bounds() + self.env_box.var_table.set_bounds(bounds) - # set initial points to sample - self._fill_init_table() + # set initial points to sample + self._fill_init_table() # set selected variables self.env_box.var_table.set_selected(vocs.variables) @@ -391,14 +395,47 @@ def generate_template_dict_from_gui(self): vocs, critical_constraints = self._compose_vocs() - # set bounds to variable range limits (avoids confusion when looking at template file) + vrange_limit_options = {} + for var in self.env_box.var_table.variables: + # set bounds to variable range limits name = next(iter(var)) if self.env_box.var_table.is_checked(name): bounds = var[name] if name in vocs.variables: vocs.variables[name] = bounds + # Record the ratio var ranges + if self.env_box.relative_to_curr.isChecked(): + # set all to self.limit_option (I don't think auto mode *currently allows + # setting different ranges for different vars) + for vname in vocs.variables: + vrange_limit_options[vname] = copy.deepcopy(self.limit_option) + + # Set vrange_limit_options based on current table info + if not self.env_box.relative_to_curr.isChecked(): + # Set each based on bounds in table --> convert to percentage of full range + var_bounds = self.env_box.var_table.export_variables() + for var_name in var_bounds: + # get bounds from table + vocs_bounds = vocs.variables[var_name] + bound_range = vocs_bounds[1] - vocs_bounds[0] + desired_bound_range = var_bounds[var_name][1] - var_bounds[var_name][0] + + # calc percentage of full range + ratio_full = desired_bound_range / bound_range + + # calc percentage of current value + # Probably a better way to get current value? + var_curr = var_bounds[var_name][0] + 0.5 * desired_bound_range + ratio_curr = float(desired_bound_range / np.abs(var_curr)) + + vrange_limit_options[var_name] = { + "limit_option_idx": 1, + "ratio_curr": ratio_curr, + "ratio_full": ratio_full, + } + template_dict = { "name": self.edit_save.text(), "description": str(self.edit_descr.toPlainText()), @@ -411,7 +448,7 @@ def generate_template_dict_from_gui(self): "name": self.env_box.cb.currentText(), "params": load_config(self.env_box.edit.toPlainText()) }, - "vrange_limit_options": self.ratio_var_ranges, + "vrange_limit_options": vrange_limit_options, "initial_point_actions": self.init_table_actions, "critical_constraint_names": critical_constraints, "vocs": vars(vocs), From 5d8fa0bd4c9a6f0bf4867909914623ee17f9af64 Mon Sep 17 00:00:00 2001 From: michaellans Date: Tue, 18 Feb 2025 08:15:14 -0800 Subject: [PATCH 14/18] Small fix to properly fill init points table if relative box is not checked --- src/badger/gui/acr/components/routine_page.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 1f0a8afb..d7bcfc84 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -356,14 +356,14 @@ def set_options_from_template(self, template_dict: dict): bounds = self.calc_auto_bounds() self.env_box.var_table.set_bounds(bounds) - # set initial points to sample - self._fill_init_table() - # set selected variables self.env_box.var_table.set_selected(vocs.variables) #self.env_box.var_table.set_bounds(vocs.variables) self.env_box.check_only_var.setChecked(True) + # set initial points to sample + self._fill_init_table() + # set objectives self.env_box.obj_table.set_selected(vocs.objectives) self.env_box.obj_table.set_rules(vocs.objectives) @@ -411,9 +411,8 @@ def generate_template_dict_from_gui(self): # setting different ranges for different vars) for vname in vocs.variables: vrange_limit_options[vname] = copy.deepcopy(self.limit_option) - - # Set vrange_limit_options based on current table info - if not self.env_box.relative_to_curr.isChecked(): + else: + # Set vrange_limit_options based on current table info # Set each based on bounds in table --> convert to percentage of full range var_bounds = self.env_box.var_table.export_variables() for var_name in var_bounds: From 0acd1d4ecf112f2b51d5b06bc226ad69bc5b342d Mon Sep 17 00:00:00 2001 From: michaellans Date: Tue, 18 Feb 2025 23:31:11 -0800 Subject: [PATCH 15/18] small bug fix to set_options_from_template --- src/badger/gui/acr/components/routine_page.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index d7bcfc84..253c3dc2 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -351,6 +351,8 @@ def set_options_from_template(self, template_dict: dict): self.ratio_var_ranges = vrange_limit_options self.init_table_actions = initial_point_actions + self.env_box.init_table.clear() + # set bounds (should this be somewhere else?) if env_name: bounds = self.calc_auto_bounds() @@ -361,8 +363,9 @@ def set_options_from_template(self, template_dict: dict): #self.env_box.var_table.set_bounds(vocs.variables) self.env_box.check_only_var.setChecked(True) - # set initial points to sample - self._fill_init_table() + if not relative_to_current: + # set initial points to sample + self._fill_init_table() # set objectives self.env_box.obj_table.set_selected(vocs.objectives) From 8e8903136d18ce69603818b4c858f003df68b96d Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 19 Feb 2025 01:03:30 -0800 Subject: [PATCH 16/18] Updated comments --- src/badger/gui/acr/components/routine_page.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 253c3dc2..3f90e22f 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -164,16 +164,6 @@ def init_ui(self): vbox_meta.addWidget(descr) descr_bar.hide() - """# Load Template Button - template_button = QWidget() - template_button.setFixedWidth(128) - hbox_name = QHBoxLayout(template_button) - hbox_name.setContentsMargins(0, 0, 0, 0) - self.load_template_button = load_template_button = QPushButton("Load Template") - hbox_name.addWidget(load_template_button, 0) - vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) - template_button.show()""" - # Save Template Button template_button = QWidget() hbox_name = QHBoxLayout(template_button) @@ -263,8 +253,8 @@ def config_logic(self): def load_template_yaml(self) -> None: """ Load data from template .yaml into template_dict dictionary. - This function expects to be called via an action from either - a QPushButton, or QComboBox with filenames as menu options + This function expects to be called via an action from + a QPushButton """ if isinstance(self.sender(), QPushButton): From d608fe40b6b047fd119e6ad37ed6770662b4284e Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 19 Feb 2025 06:03:56 -0800 Subject: [PATCH 17/18] linting --- src/badger/gui/acr/components/routine_page.py | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 3f90e22f..68d30769 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -99,9 +99,9 @@ def __init__(self): # Trigger the re-rendering of the environment box self.env_box.relative_to_curr.setChecked(True) - + # Template path - #template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + # template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") self.template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" def init_ui(self): @@ -168,7 +168,9 @@ def init_ui(self): template_button = QWidget() hbox_name = QHBoxLayout(template_button) hbox_name.setContentsMargins(0, 0, 0, 0) - self.save_template_button = save_template_button = QPushButton("Save as Template") + self.save_template_button = save_template_button = QPushButton( + "Save as Template" + ) save_template_button.setFixedWidth(128) hbox_name.addWidget(save_template_button, alignment=Qt.AlignRight) vbox_meta.addWidget(template_button, alignment=Qt.AlignBottom) @@ -184,7 +186,9 @@ def init_ui(self): # vbox.addWidget(group_meta) # Env box - self.BADGER_PLUGIN_ROOT = BADGER_PLUGIN_ROOT = config_singleton.read_value("BADGER_PLUGIN_ROOT") + self.BADGER_PLUGIN_ROOT = BADGER_PLUGIN_ROOT = config_singleton.read_value( + "BADGER_PLUGIN_ROOT" + ) env_dict_dir = os.path.join( BADGER_PLUGIN_ROOT, "environments", "env_colors.yaml" ) @@ -196,7 +200,8 @@ def init_ui(self): self.env_box = BadgerEnvBox(env_dict, None, self.envs) scroll_area = QScrollArea() scroll_area.setFrameShape(QScrollArea.NoFrame) - scroll_area.setStyleSheet(""" + scroll_area.setStyleSheet( + """ QScrollArea { border: none; /* Remove border */ margin: 0px; /* Remove margin */ @@ -205,7 +210,8 @@ def init_ui(self): QScrollArea > QWidget { margin: 0px; /* Remove margin inside */ } - """) + """ + ) scroll_content_env = QWidget() scroll_layout_env = QVBoxLayout(scroll_content_env) scroll_layout_env.setContentsMargins(0, 0, 15, 0) @@ -267,7 +273,7 @@ def load_template_yaml(self) -> None: "YAML Files (*.yaml);;All Files (*)", options=options, ) - + if not template_path: return @@ -276,11 +282,13 @@ def load_template_yaml(self) -> None: with open(template_path, "r") as stream: template_dict = yaml.safe_load(stream) self.set_options_from_template(template_dict=template_dict) - self.sig_load_template.emit(f"Options loaded from template: {os.path.basename(template_path)}") + self.sig_load_template.emit( + f"Options loaded from template: {os.path.basename(template_path)}" + ) except (FileNotFoundError, yaml.YAMLError) as e: print(f"Error loading template: {e}") return - + def set_options_from_template(self, template_dict: dict): """ Fills in routine_page GUI with relevant info from template_dict @@ -295,13 +303,15 @@ def set_options_from_template(self, template_dict: dict): generator_name = template_dict["generator"]["name"] env_name = template_dict["environment"]["name"] vrange_limit_options = template_dict["vrange_limit_options"] - initial_point_actions = template_dict["initial_point_actions"] # should be type: add_curr + initial_point_actions = template_dict[ + "initial_point_actions" + ] # should be type: add_curr critical_constraint_names = template_dict["critical_constraint_names"] env_params = template_dict["environment"]["params"] except KeyError as e: QMessageBox.warning(self, "Error", f"Missing key in template: {e}") return - + # set vocs vocs = VOCS( variables=template_dict["vocs"]["variables"], @@ -319,7 +329,9 @@ def set_options_from_template(self, template_dict: dict): i = self.generators.index(generator_name) self.generator_box.cb.setCurrentIndex(i) - filtered_config = filter_generator_config(generator_name, template_dict["generator"]) + filtered_config = filter_generator_config( + generator_name, template_dict["generator"] + ) self.generator_box.edit.setPlainText(get_yaml_string(filtered_config)) # set environment @@ -333,16 +345,16 @@ def set_options_from_template(self, template_dict: dict): # make sure gui checkbox state matches yaml option if not self.env_box.relative_to_curr.isChecked(): self.env_box.relative_to_curr.setChecked(True) - + else: if self.env_box.relative_to_curr.isChecked(): self.env_box.relative_to_curr.setChecked(False) self.ratio_var_ranges = vrange_limit_options self.init_table_actions = initial_point_actions - + self.env_box.init_table.clear() - + # set bounds (should this be somewhere else?) if env_name: bounds = self.calc_auto_bounds() @@ -350,7 +362,7 @@ def set_options_from_template(self, template_dict: dict): # set selected variables self.env_box.var_table.set_selected(vocs.variables) - #self.env_box.var_table.set_bounds(vocs.variables) + # self.env_box.var_table.set_bounds(vocs.variables) self.env_box.check_only_var.setChecked(True) if not relative_to_current: @@ -374,22 +386,22 @@ def set_options_from_template(self, template_dict: dict): self.env_box.list_con.clear() # set observables - observables = vocs.observable_names + observables = vocs.observable_names if len(observables): for name_sta in observables: self.add_state(name_sta) else: self.env_box.list_obs.clear() - + def generate_template_dict_from_gui(self): """ Generate a template dictionary from the current state of the GUI """ vocs, critical_constraints = self._compose_vocs() - + vrange_limit_options = {} - + for var in self.env_box.var_table.variables: # set bounds to variable range limits name = next(iter(var)) @@ -400,7 +412,7 @@ def generate_template_dict_from_gui(self): # Record the ratio var ranges if self.env_box.relative_to_curr.isChecked(): - # set all to self.limit_option (I don't think auto mode *currently allows + # set all to self.limit_option (I don't think auto mode *currently allows # setting different ranges for different vars) for vname in vocs.variables: vrange_limit_options[vname] = copy.deepcopy(self.limit_option) @@ -413,10 +425,10 @@ def generate_template_dict_from_gui(self): vocs_bounds = vocs.variables[var_name] bound_range = vocs_bounds[1] - vocs_bounds[0] desired_bound_range = var_bounds[var_name][1] - var_bounds[var_name][0] - + # calc percentage of full range ratio_full = desired_bound_range / bound_range - + # calc percentage of current value # Probably a better way to get current value? var_curr = var_bounds[var_name][0] + 0.5 * desired_bound_range @@ -427,29 +439,29 @@ def generate_template_dict_from_gui(self): "ratio_curr": ratio_curr, "ratio_full": ratio_full, } - + template_dict = { "name": self.edit_save.text(), "description": str(self.edit_descr.toPlainText()), "relative_to_current": self.env_box.relative_to_curr.isChecked(), "generator": { "name": self.generator_box.cb.currentText(), - - } | load_config(self.generator_box.edit.toPlainText()), + } + | load_config(self.generator_box.edit.toPlainText()), "environment": { "name": self.env_box.cb.currentText(), - "params": load_config(self.env_box.edit.toPlainText()) + "params": load_config(self.env_box.edit.toPlainText()), }, "vrange_limit_options": vrange_limit_options, "initial_point_actions": self.init_table_actions, "critical_constraint_names": critical_constraints, - "vocs": vars(vocs), + "vocs": vars(vocs), "badger_version": get_badger_version(), "xopt_version": get_xopt_version(), - } + } return template_dict - + def save_template_yaml(self): """ Save the current routine as a template .yaml file @@ -472,11 +484,13 @@ def save_template_yaml(self): try: with open(template_path, "w") as stream: yaml.dump(template_dict, stream) - self.sig_save_template.emit(f"Current routine options saved to template: {os.path.basename(template_path)}") + self.sig_save_template.emit( + f"Current routine options saved to template: {os.path.basename(template_path)}" + ) except (FileNotFoundError, yaml.YAMLError) as e: print(f"Error saving template: {e}") return - + def refresh_ui(self, routine: Routine = None, silent: bool = False): self.routine = routine # save routine for future reference From 2933c3ed3fd747c2a3adbc6b9df5db9136106904 Mon Sep 17 00:00:00 2001 From: mlans Date: Wed, 26 Feb 2025 15:09:36 -0800 Subject: [PATCH 18/18] changed template_dir to BADGER_PLUGIN_ROOT --- src/badger/gui/acr/components/routine_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/badger/gui/acr/components/routine_page.py b/src/badger/gui/acr/components/routine_page.py index 6612c3ff..4bbe3fd8 100644 --- a/src/badger/gui/acr/components/routine_page.py +++ b/src/badger/gui/acr/components/routine_page.py @@ -101,8 +101,8 @@ def __init__(self): self.env_box.relative_to_curr.setChecked(True) # Template path - # template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") - self.template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" + self.template_dir = os.path.join(self.BADGER_PLUGIN_ROOT, "templates") + # self.template_dir = "/home/physics/mlans/workspace/badger_test/Badger/src/badger/built_in_plugins/templates" def init_ui(self): config_singleton = init_settings()