From 997737a5604e4594ff8d39dd70f77b93bdee017a Mon Sep 17 00:00:00 2001 From: mlau154 Date: Fri, 10 Nov 2023 11:40:08 -0600 Subject: [PATCH] Implemented pkl file load --- install/install.py | 2 + pymead/gui/gui.py | 69 ++++++++++-- pymead/gui/gui_settings/menu.json | 3 +- .../gui_settings/q_settings_descriptions.json | 3 +- pymead/gui/input_dialog.py | 106 +++++++++++++++++- 5 files changed, 173 insertions(+), 10 deletions(-) diff --git a/install/install.py b/install/install.py index 58041a5c..5ec3d6ff 100644 --- a/install/install.py +++ b/install/install.py @@ -129,6 +129,8 @@ def run(): else: raise ValueError("iss setup install command failed") + print(f"Install complete. Output is in {install_dir}") + if __name__ == "__main__": run() diff --git a/pymead/gui/gui.py b/pymead/gui/gui.py index 2903fef9..d6ddd020 100644 --- a/pymead/gui/gui.py +++ b/pymead/gui/gui.py @@ -13,6 +13,8 @@ from collections import namedtuple import multiprocessing as mp +from pymoo.factory import get_decomposition + from pymead.gui.rename_popup import RenamePopup from pymead.gui.main_icon_toolbar import MainIconToolbar @@ -34,7 +36,7 @@ from pymead.gui.input_dialog import LoadDialog, SaveAsDialog, OptimizationSetupDialog, \ MultiAirfoilDialog, ColorInputDialog, ExportCoordinatesDialog, ExportControlPointsDialog, AirfoilPlotDialog, \ AirfoilMatchingDialog, MSESFieldPlotDialog, ExportIGESDialog, XFOILDialog, NewMEADialog, EditBoundsDialog, \ - ExitDialog, ScreenshotDialog + ExitDialog, ScreenshotDialog, LoadAirfoilAlgFile from pymead.gui.pymeadPColorMeshItem import PymeadPColorMeshItem from pymead.gui.analysis_graph import AnalysisGraph from pymead.gui.parameter_tree import MEAParamTree @@ -489,6 +491,20 @@ def auto_range_geometry(self): x_data_range, y_data_range = self.mea.get_curve_bounds() self.v.getViewBox().setRange(xRange=x_data_range, yRange=y_data_range) + def update_airfoil_parameters_from_vector(self, param_vec: np.ndarray): + for airfoil in self.mea.airfoils.values(): + airfoil.airfoil_graph.airfoil_parameters = self.param_tree_instance.p.param('Airfoil Parameters') + + try: + self.mea.update_parameters(param_vec) + except: + self.disp_message_box("Could not load parameters into airfoil. Check that the current airfoil system" + " displayed matches the one used in the optimization.") + return + + self.param_tree_instance.plot_change_recursive( + self.param_tree_instance.p.param('Airfoil Parameters').child('Custom').children()) + def import_parameter_list(self): """This function imports a list of parameters normalized by their bounds""" file_filter = "DAT Files (*.dat)" @@ -496,12 +512,51 @@ def import_parameter_list(self): if dialog.exec_(): file_name = dialog.selectedFiles()[0] q_settings.setValue(dialog.settings_var, os.path.dirname(file_name)) - parameter_list = np.loadtxt(file_name).tolist() - for airfoil in self.mea.airfoils.values(): - airfoil.airfoil_graph.airfoil_parameters = self.param_tree_instance.p.param('Airfoil Parameters') - self.mea.update_parameters(parameter_list) - self.param_tree_instance.plot_change_recursive( - self.param_tree_instance.p.param('Airfoil Parameters').child('Custom').children()) + param_vec = np.loadtxt(file_name).tolist() + self.update_airfoil_parameters_from_vector(param_vec) + + def import_algorithm_pkl_file(self): + dialog = LoadAirfoilAlgFile(self) + if dialog.exec_(): + inputs = dialog.getInputs() + + try: + alg = load_data(inputs["pkl_file"]) + except: + self.disp_message_box("Could not load .pkl file. Check that the file selected is of the form" + " algorithm_gen_XX.pkl.") + return + + try: + X = alg.opt.get("X") + except AttributeError: + self.disp_message_box("Algorithm file not recognized. Check that the file selected is of the form" + " algorithm_gen_XX.pkl.") + return + + if X.shape[0] == 1: # If single-objective: + x = X[0, :] + elif X.shape[0] == 0: # If the optimization result is empty: + self.disp_message_box("Empty optimization result") + return + else: # If multi-objective + if inputs["use_index"]: + x = X[inputs["index"], :] + elif inputs["use_weights"]: + F = alg.opt.get("F") + decomp = get_decomposition("asf") + + if len(inputs["weights"]) != F.shape[0]: + self.disp_message_box(f"Length of the requested weight list ({len(inputs['weights'])}) does" + f" not match the number of objective functions ({F.shape[0]})") + return + + IDX = decomp.do(F, inputs["weights"]).argmin() + x = X[IDX, :] + else: + raise ValueError("Either 'index' or 'weights' must be selected in the dialog") + + self.update_airfoil_parameters_from_vector(x) def export_parameter_list(self): """This function imports a list of parameters normalized by their bounds""" diff --git a/pymead/gui/gui_settings/menu.json b/pymead/gui/gui_settings/menu.json index c818a014..69722b84 100644 --- a/pymead/gui/gui_settings/menu.json +++ b/pymead/gui/gui_settings/menu.json @@ -5,7 +5,8 @@ "Save As": "save_as_mea", "Save": ["save_mea", "Ctrl+S"], "Import": { - "Parameter List": "import_parameter_list" + "Parameter List": "import_parameter_list", + "Optimized Airfoil": "import_algorithm_pkl_file" }, "Export": { "Airfoil Coordinates": "export_coordinates", diff --git a/pymead/gui/gui_settings/q_settings_descriptions.json b/pymead/gui/gui_settings/q_settings_descriptions.json index 947af1b1..2c93eb36 100644 --- a/pymead/gui/gui_settings/q_settings_descriptions.json +++ b/pymead/gui/gui_settings/q_settings_descriptions.json @@ -3,5 +3,6 @@ "parameter_list_default_open_location": "Directory where import_parameter_list() starts by default", "geometry_plot_default_open_location": "Directory where plot_geometry() starts by default", "dark_theme_checked": "set_theme('dark') used if true, else set_theme('light')", - "ga-settings-dir": "Directory where GAGeneralSettingsDialogWidget.load_opt_settings starts" + "ga-settings-dir": "Directory where GAGeneralSettingsDialogWidget.load_opt_settings starts", + "pkl_file_dir": "Directory where import_algorithm_pkl_file starts" } \ No newline at end of file diff --git a/pymead/gui/input_dialog.py b/pymead/gui/input_dialog.py index fab43642..543a0524 100644 --- a/pymead/gui/input_dialog.py +++ b/pymead/gui/input_dialog.py @@ -5,7 +5,7 @@ import numpy as np from typing import List from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QFormLayout, QDoubleSpinBox, QComboBox, QSpinBox, \ - QTabWidget, QLabel, QMessageBox, QCheckBox, QVBoxLayout, QWidget, QGridLayout, QPushButton, QListView + QTabWidget, QLabel, QMessageBox, QCheckBox, QVBoxLayout, QWidget, QGridLayout, QPushButton, QListView, QRadioButton from PyQt5.QtCore import QEvent, Qt, pyqtSignal from PyQt5.QtGui import QStandardItem, QStandardItemModel import tempfile @@ -2025,6 +2025,110 @@ def __init__(self, parent, settings_var: str, file_filter: str = "JMEA Files (*. self.setDirectory(path) +class LoadAirfoilAlgFileWidget(QWidget): + def __init__(self, parent): + super().__init__(parent) + layout = QGridLayout(self) + + self.pkl_line = QLineEdit("", self) + self.pkl_selection_button = QPushButton("Choose File", self) + self.index_button = QRadioButton("Use Index", self) + self.weight_button = QRadioButton("Use Weights", self) + self.index_spin = QSpinBox(self) + self.index_spin.setValue(0) + self.index_spin.setMaximum(9999) + self.weight_line = QLineEdit(self) + + self.index_button.toggled.connect(self.index_selected) + self.weight_button.toggled.connect(self.weight_selected) + + self.weight_line.textChanged.connect(self.weights_changed) + self.pkl_selection_button.clicked.connect(self.choose_file_clicked) + + self.weight_line.setText("0.5,0.5") + + self.index_button.toggle() + + layout.addWidget(self.pkl_line, 0, 0) + layout.addWidget(self.pkl_selection_button, 0, 1) + layout.addWidget(self.index_button, 1, 0) + layout.addWidget(self.weight_button, 1, 1) + layout.addWidget(self.index_spin, 2, 0) + layout.addWidget(self.weight_line, 2, 1) + + self.setLayout(layout) + + def choose_file_clicked(self): + dialog = LoadDialog(self, settings_var="pkl_file_dir", file_filter="PKL files (*.pkl)") + + if dialog.exec_(): + file_name = dialog.selectedFiles()[0] + self.pkl_line.setText(file_name) + file_name_parent_dir = os.path.dirname(file_name) + q_settings.setValue(dialog.settings_var, file_name_parent_dir) + + def index_selected(self): + self.index_spin.setReadOnly(False) + self.weight_line.setReadOnly(True) + + def weight_selected(self): + self.index_spin.setReadOnly(True) + self.weight_line.setReadOnly(False) + + def weights_changed(self, new_text: str): + weights = self.validate_weights(new_text) + if len(weights) > 0: + self.weight_line.setStyleSheet("QLineEdit { background: rgba(16,201,87,50) }") + else: + self.weight_line.setStyleSheet("QLineEdit { background: rgba(176,25,25,50) }") + + @staticmethod + def validate_weights(text: str): + text = text.strip() + text_list = text.split(",") + try: + weights = [float(t) for t in text_list] + weight_sum = sum(weights) + if not np.isclose(weight_sum, 1.0, rtol=1e-12): + return [] + except: + return [] + + return weights + + def getInputs(self): + return { + "pkl_file": self.pkl_line.text(), + "use_index": self.index_button.isChecked(), + "use_weights": self.weight_button.isChecked(), + "index": self.index_spin.value(), + "weights": self.validate_weights(self.weight_line.text()) + } + + +class LoadAirfoilAlgFile(QDialog): + def __init__(self, parent): + super().__init__(parent=parent) + + self.setWindowTitle("Load Optimized Airfoil") + self.setFont(self.parent().font()) + + buttonBox = QDialogButtonBox(self) + buttonBox.addButton(QDialogButtonBox.Ok) + buttonBox.addButton(QDialogButtonBox.Cancel) + self.grid_layout = QGridLayout(self) + + self.load_airfoil_alg_file_widget = LoadAirfoilAlgFileWidget(self) + self.grid_layout.addWidget(self.load_airfoil_alg_file_widget, 0, 0) + self.grid_layout.addWidget(buttonBox, self.grid_layout.rowCount(), 0) + + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + + def getInputs(self): + return self.load_airfoil_alg_file_widget.getInputs() + + class SaveAsDialog(QFileDialog): def __init__(self, parent, file_filter: str = "JMEA Files (*.jmea)"): super().__init__(parent=parent)