Skip to content

Commit

Permalink
Optimization abort button, output folder link, status bar opt updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mlauer154 committed Sep 5, 2023
1 parent 23e4570 commit 5d07bd9
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 78 deletions.
3 changes: 3 additions & 0 deletions pymead/gui/dockable_tab_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ def add_new_tab_widget(self, widget, name):
self.setCentralWidget(dw)

def on_tab_closed(self, name: str, event: QCloseEvent):
if name == "Geometry":
event.ignore()
return
idx = self.names.index(name)
self.names.pop(idx)
self.dock_widgets.pop(idx)
Expand Down
81 changes: 68 additions & 13 deletions pymead/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def __init__(self, path=None, parent=None):
# super().__init__(flags=Qt.FramelessWindowHint)
super().__init__(parent=parent)
# self.setWindowFlags(Qt.CustomizeWindowHint)
print(f"Running GUI with {os.getpid() = }")
self.pool = None
self.current_opt_folder = None
self.menu_bar = None
self.path = path
# single_element_inviscid(np.array([[1, 0], [0, 0], [1, 0]]), 0.0)
Expand Down Expand Up @@ -137,7 +140,7 @@ def __init__(self, path=None, parent=None):
self.n_analyses = 0
self.n_converged_analyses = 0
self.threadpool = QThreadPool().globalInstance()
self.threadpool.setMaxThreadCount(2)
self.threadpool.setMaxThreadCount(4)
self.pens = [('#d4251c', Qt.SolidLine), ('darkorange', Qt.SolidLine), ('gold', Qt.SolidLine),
('limegreen', Qt.SolidLine), ('cyan', Qt.SolidLine), ('mediumpurple', Qt.SolidLine),
('deeppink', Qt.SolidLine), ('#d4251c', Qt.DashLine), ('darkorange', Qt.DashLine),
Expand Down Expand Up @@ -272,8 +275,6 @@ def closeEvent(self, a0) -> None:
a0.ignore()
break

# TODO: Make an "abort" button for optimization

def on_tab_closed(self, name: str, event: QCloseEvent):
if name == "Geometry":
event.ignore() # Do not allow user to close the geometry window
Expand All @@ -282,12 +283,15 @@ def on_tab_closed(self, name: str, event: QCloseEvent):
self.n_converged_analyses = 0
elif name == "Opt. Airfoil":
self.opt_airfoil_graph = None
self.opt_airfoil_plot_handles = []
elif name == "Drag":
self.drag_graph = None
elif name == "Parallel Coordinates":
self.parallel_coords_graph = None
self.parallel_coords_plot_handles = []
elif name == "Cp":
self.Cp_graph = None
self.Cp_graph_plot_handles = []

@pyqtSlot(str)
def setStatusBarText(self, message: str):
Expand Down Expand Up @@ -702,10 +706,14 @@ def add_airfoil(self, airfoil: Airfoil):
def disp_message_box(self, message: str, message_mode: str = 'error'):
disp_message_box(message, self, message_mode=message_mode)

def output_area_text(self, text: str, mode: str = 'plain'):
def output_area_text(self, text: str, mode: str = 'plain', mono: bool = True):
prepend_html = f"<head><style>body {{font-family: DejaVu Sans Mono;}}</style>" \
f"</head><body><p><font size='4'>&#8203;</font></p></body>"
previous_cursor = self.text_area.textCursor()
self.text_area.moveCursor(QTextCursor.End)
if mode == 'plain':
if mode == "plain" and mono:
self.text_area.insertHtml(prepend_html)
self.text_area.insertPlainText(text)
elif mode == 'html':
self.text_area.insertHtml(text)
Expand Down Expand Up @@ -1202,6 +1210,7 @@ def optimization_accepted(self):
opt_settings['Genetic Algorithm']['opt_dir_name'])

param_dict['opt_dir'] = opt_dir
self.current_opt_folder = opt_dir.replace(os.sep, "/")

name_base = 'ga_airfoil'
name = [f"{name_base}_{i}" for i in range(opt_settings['Genetic Algorithm']['n_offspring'])]
Expand All @@ -1219,6 +1228,8 @@ def optimization_accepted(self):
if opt_settings['General Settings']['warm_start_active']:
param_dict['warm_start_generation'] = calculate_warm_start_index(
opt_settings['General Settings']['warm_start_generation'], opt_dir)
if param_dict['warm_start_generation'] == 0:
opt_settings['General Settings']['warm_start_active'] = False
param_dict_save = deepcopy(param_dict)
if not opt_settings['General Settings']['warm_start_active']:
save_data(param_dict_save, os.path.join(opt_dir, 'param_dict.json'))
Expand Down Expand Up @@ -1252,33 +1263,73 @@ def run_shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict
self.worker.signals.result.connect(self.shape_opt_result_callback_fn)
self.worker.signals.finished.connect(self.shape_opt_finished_callback_fn)
self.worker.signals.error.connect(self.shape_opt_error_callback_fn)
self.worker.signals.message.connect(self.message_callback_fn)
self.worker.signals.text.connect(self.text_area_callback_fn)
self.worker.signals.pool.connect(self.set_pool)
self.threadpool.start(self.worker)

def stop_optimization(self):
if self.pool is not None:
self.pool.terminate()
self.output_area_text("Optimization terminated. ")
self.output_area_text(self.generate_output_folder_link_text(self.current_opt_folder), mode="html")
self.pool = None
self.current_opt_folder = None

@staticmethod
def generate_output_folder_link_text(folder: str):
return f"<a href='{folder}' style='font-family:DejaVu Sans Mono; " \
f"color: #1FBBCC; font-size: 14px;'>Open output folder</a>\n"

def set_pool(self, pool_obj: object):
print(f"Setting pool! {pool_obj = }")
self.pool = pool_obj

@staticmethod
def shape_opt_progress_callback_fn(progress_object: object):
if isinstance(progress_object, OptCallback):
progress_object.exec_callback()

def shape_opt_finished_callback_fn(self):
self.output_area_text("Completed aerodynamic shape optimization.\n\n")
self.forces_dict = {}
self.pool = None
self.status_bar.showMessage("Optimization Complete!", 3000)
self.output_area_text("Completed aerodynamic shape optimization. ")
self.output_area_text(self.generate_output_folder_link_text(self.current_opt_folder), mode="html")
self.output_area_text("\n\n")
self.current_opt_folder = None
self.status_bar.showMessage("")
# self.finished_optimization = True

def shape_opt_result_callback_fn(self, result_object: object):
pass

def message_callback_fn(self, message: str):
self.status_bar.showMessage(message)

def text_area_callback_fn(self, message: str):
self.output_area_text(message)

def shape_opt_error_callback_fn(self, error_tuple: tuple):
self.output_area_text(f"Error. Error = {error_tuple}\n")

def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, progress_callback):
self.output_area_text(f"\nBeginning aerodynamic shape optimization with "
f"{param_dict['num_processors']} processors...\n")
def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict,
progress_callback):
def start_message(warm_start: bool):
first_word = "Resuming" if warm_start else "Beginning"
return f"\n{first_word} aerodynamic shape optimization with {param_dict['num_processors']} processors...\n"

self.worker.signals.text.emit(start_message(opt_settings["General Settings"]["warm_start_active"]))

Config.show_compile_hint = False
forces = []
ref_dirs = get_reference_directions("energy", param_dict['n_obj'], param_dict['n_ref_dirs'],
seed=param_dict['seed'])
mea_object = MEA.generate_from_param_dict(mea)
parameter_list, _ = mea_object.extract_parameters()
num_parameters = len(parameter_list)
self.worker.signals.text.emit(f"Number of active and unlinked design variables: {num_parameters}\n")
# self.output_area_text(f"Number of active and unlinked design variables: {num_parameters}\n")

problem = TPAIOPT(n_var=param_dict['n_var'], n_obj=param_dict['n_obj'], n_constr=param_dict['n_constr'],
xl=param_dict['xl'], xu=param_dict['xu'], param_dict=param_dict)
Expand All @@ -1296,7 +1347,7 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
population = Population(param_dict=param_dict, generation=0, parents=parents,
mea=mea, verbose=param_dict['verbose'])

population.eval_pop_fitness()
population.eval_pop_fitness(sig=self.worker.signals.message, pool_sig=self.worker.signals.pool)
print(f"Finished evaluating population fitness. Continuing...")

new_X = None
Expand Down Expand Up @@ -1330,7 +1381,7 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
G = np.row_stack((G, np.array([
constraint.value for constraint in self.constraints])))

print(f"{J = }, {self.objectives = }")
# print(f"{J = }, {self.objectives = }")

if new_X.ndim == 1:
new_X = np.array([new_X])
Expand Down Expand Up @@ -1423,7 +1474,7 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
genes=individual) for idx, individual in enumerate(X)]
population = Population(problem.param_dict, generation=n_generation,
parents=parents, verbose=param_dict['verbose'], mea=mea)
population.eval_pop_fitness()
population.eval_pop_fitness(sig=self.worker.signals.message, pool_sig=self.worker.signals.pool)

for chromosome in population.converged_chromosomes:
forces.append(chromosome.forces)
Expand All @@ -1449,7 +1500,7 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
G = np.row_stack((G, np.array([
constraint.value for constraint in self.constraints])))

print(f"{J = }, {self.objectives = }")
# print(f"{J = }, {self.objectives = }")

algorithm.evaluator.n_eval += param_dict['num_processors']

Expand Down Expand Up @@ -1490,8 +1541,12 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr

# print(f"{algorithm.opt.get('F') = }")

warm_start_gen = None
if opt_settings["General Settings"]["warm_start_active"]:
warm_start_gen = param_dict["warm_start_generation"]

progress_callback.emit(TextCallback(parent=self, text_list=algorithm.display.progress_dict,
completed=not algorithm.has_next()))
completed=not algorithm.has_next(), warm_start_gen=warm_start_gen))

if len(self.objectives) == 1:
if n_generation > 1:
Expand Down
7 changes: 7 additions & 0 deletions pymead/gui/gui_settings/buttons.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
"checked-by-default": false,
"function": "on_pos_constraint_pressed"
},
"stop-optimization": {
"icon": "stop.png",
"status_tip": "Terminate optimization",
"checkable": false,
"checked-by-default": false,
"function": "on_stop_button_pressed"
},
"template": {
"icon": "picture.png",
"status_tip": "A helpful hint",
Expand Down
4 changes: 4 additions & 0 deletions pymead/gui/main_icon_toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def add_all_buttons(self):
button.toggle()
self.addWidget(button)

def on_stop_button_pressed(self):
if hasattr(self.parent, "pool") and self.parent.pool is not None:
self.parent.stop_optimization()

def on_grid_button_pressed(self):
# import pyqtgraph as pg
if hasattr(self.parent.dockable_tab_window, "current_dock_widget"):
Expand Down
6 changes: 4 additions & 2 deletions pymead/gui/text_area.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtWidgets import QTextEdit, QTextBrowser
from PyQt5.QtGui import QTextCursor, QColor, QFontDatabase


class ConsoleTextArea(QTextEdit):
class ConsoleTextArea(QTextBrowser):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
Expand All @@ -16,3 +16,5 @@ def __init__(self, parent=None):
self.moveCursor(QTextCursor.End)
# self.setTextColor(QColor("#13294B"))
self.setFixedHeight(200)
self.setOpenLinks(True)
self.setOpenExternalLinks(True)
4 changes: 4 additions & 0 deletions pymead/gui/worker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from PyQt5.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot
from multiprocessing.pool import Pool
import traceback
import sys

Expand All @@ -11,6 +12,9 @@ class WorkerSignals(QObject):
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(object)
message = pyqtSignal(str)
text = pyqtSignal(str)
pool = pyqtSignal(object)


class Worker(QRunnable):
Expand Down
Binary file added pymead/icons/stop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions pymead/optimization/opt_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ def exec_callback(self):


class TextCallback(OptCallback):
def __init__(self, parent, text_list: typing.List[list], completed: bool = False):
def __init__(self, parent, text_list: typing.List[list], completed: bool = False,
warm_start_gen: int or None = None):
super().__init__(parent=parent)
self.parent = parent
self.text_list = text_list
self.names = [attr[0] for attr in self.text_list]
self.widths = [attr[2] for attr in self.text_list]
self.values = [attr[1] for attr in self.text_list]
self.completed = completed
self.warm_start_gen = warm_start_gen

def generate_header(self):
t = ""
Expand All @@ -52,15 +54,15 @@ def stringify_text_list(self):
t += f"{v.center(w + 2)}|"
if idx == len(self.widths) - 1:
t += "|"
print(f"Row length = {len(t)}")
# print(f"Row length = {len(t)}")
return t

@staticmethod
def generate_closer(length: int):
return "="*length

def exec_callback(self):
if self.values[0] == 1:
if self.values[0] == 1 or (self.warm_start_gen is not None and self.values[0] == self.warm_start_gen + 1):
# font = self.parent.text_area.font()
# print(QFontDatabase().families())
# # font.setFamily("DejaVu Sans Mono")
Expand Down Expand Up @@ -159,7 +161,6 @@ def __init__(self, parent, background_color: str = 'w', design_idx: int = 0):
self.Cd = self.forces['Cd']
self.Cdp = self.forces['Cdp']
self.Cdf = self.forces['Cdf']
print(f"{self.Cd = }")
self.design_idx = design_idx

def exec_callback(self):
Expand Down
6 changes: 5 additions & 1 deletion pymead/optimization/opt_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ def calculate_warm_start_index(warm_start_generation: int, warm_start_directory:
if warm_start_generation not in generations and warm_start_generation != -1:
raise ValueError(f'Invalid warm start generation. A value of {warm_start_generation} was input, and the valid '
f'generations in the directory are {generations}')
warm_start_index = generations[warm_start_generation]
if len(generations) > 0:
warm_start_index = generations[warm_start_generation]
else:
warm_start_index = 0

return warm_start_index


Expand Down
Loading

0 comments on commit 5d07bd9

Please sign in to comment.