Skip to content

Commit

Permalink
Generalization + prettification + GUI text output of optimization int…
Browse files Browse the repository at this point in the history
…ermediate results, monospace text area font
  • Loading branch information
mlauer154 committed Sep 1, 2023
1 parent e4a577b commit 32f7b30
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 71 deletions.
85 changes: 33 additions & 52 deletions pymead/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from PyQt5.QtWidgets import QMainWindow, QApplication, QVBoxLayout, QHBoxLayout, \
QWidget, QMenu, QStatusBar, QAction, QGraphicsScene, QGridLayout, QProgressBar
from PyQt5.QtGui import QIcon, QFont, QFontDatabase, QPainter, QCloseEvent
from PyQt5.QtGui import QIcon, QFont, QFontDatabase, QPainter, QCloseEvent, QTextCursor
from PyQt5.QtCore import QEvent, QObject, Qt, QThreadPool
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import pyqtSlot
Expand Down Expand Up @@ -64,7 +64,7 @@
from pymead.gui.message_box import disp_message_box
from pymead.gui.worker import Worker
from pymead.optimization.opt_callback import PlotAirfoilCallback, ParallelCoordsCallback, OptCallback, \
DragPlotCallbackXFOIL, CpPlotCallbackXFOIL, DragPlotCallbackMSES, CpPlotCallbackMSES
DragPlotCallbackXFOIL, CpPlotCallbackXFOIL, DragPlotCallbackMSES, CpPlotCallbackMSES, TextCallback
from pymead.gui.input_dialog import convert_dialog_to_mset_settings, convert_dialog_to_mses_settings, \
convert_dialog_to_mplot_settings
from pymead.gui.airfoil_statistics import AirfoilStatisticsDialog, AirfoilStatistics
Expand Down Expand Up @@ -223,7 +223,10 @@ def __init__(self, path=None, parent=None):
self.output_area_text(f"<font color='#1fbbcc' size='5'>pymead</font> <font size='5'>version</font> "
f"<font color='#44e37e' size='5'>{__version__}</font>",
mode='html')
self.output_area_text('\n')
self.output_area_text(
f"<head><style>body {{font-family: DejaVu Sans Mono;}}</style></head><body><p><font size='4'>&#8203;</font></p></body>",
mode="html")
self.output_area_text('\n\n')
# self.output_area_text("<font color='#ffffff' size='3'>\n\n</font>", mode='html')
airfoil = Airfoil(base_airfoil_params=BaseAirfoilParams(dx=Param(0.0), dy=Param(0.0)))
self.add_airfoil(airfoil)
Expand Down Expand Up @@ -693,12 +696,15 @@ 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'):
previous_cursor = self.text_area.textCursor()
self.text_area.moveCursor(QTextCursor.End)
if mode == 'plain':
self.text_area.insertPlainText(text)
elif mode == 'html':
self.text_area.insertHtml(text)
else:
raise ValueError('Mode must be \'plain\' or \'html\'')
self.text_area.setTextCursor(previous_cursor)
sb = self.text_area.verticalScrollBar()
sb.setValue(sb.maximum())

Expand Down Expand Up @@ -839,20 +845,19 @@ def single_airfoil_viscous_analysis(self):
if not aero_data['converged'] or aero_data['errored_out'] or aero_data['timed_out']:
self.disp_message_box("XFOIL Analysis Failed", message_mode='error')
self.output_area_text(
f"<font size='4'>[{self.n_analyses:2.0f}] Converged = {aero_data['converged']} | Errored out = "
f"{aero_data['errored_out']} | Timed out = {aero_data['timed_out']}</font>", mode='html')
f"[{str(self.n_analyses).zfill(2)}] XFOIL Converged = {aero_data['converged']} | Errored out = "
f"{aero_data['errored_out']} | Timed out = {aero_data['timed_out']}")
self.output_area_text('\n')
else:
self.output_area_text(
f"<font size='4'>[{str(self.n_analyses).zfill(2)}] ({xfoil_settings['airfoil']}, "
f"[{str(self.n_analyses).zfill(2)}] XFOIL ({xfoil_settings['airfoil']}, "
f"\u03b1 = {aero_data['alf']:.3f}, Re = {xfoil_settings['Re']:.3E}, "
f"Ma = {xfoil_settings['Ma']:.3f}): "
f"Cl = {aero_data['Cl']:+7.4f} | Cd = {aero_data['Cd']:.5f} (Cdp = {aero_data['Cdp']:.5f}, "
f"Cdf = {aero_data['Cdf']:.5f}) | Cm = {aero_data['Cm']:+7.4f} "
f"| L/D = {aero_data['L/D']:+8.4f}</font>".replace("-", "\u2212"),
mode='html')
f"Cl = {aero_data['Cl']:+7.4f} | Cd = {aero_data['Cd']:+.5f} | Cm = {aero_data['Cm']:+7.4f} "
f"| L/D = {aero_data['L/D']:+8.4f}".replace("-", "\u2212"))
self.output_area_text('\n')
sb = self.text_area.verticalScrollBar()
bar = self.text_area.verticalScrollBar()
sb = bar
sb.setValue(sb.maximum())

if aero_data['converged'] and not aero_data['errored_out'] and not aero_data['timed_out']:
Expand Down Expand Up @@ -920,16 +925,18 @@ def multi_airfoil_analysis(self, mset_settings: dict, mses_settings: dict,
if not aero_data['converged'] or aero_data['errored_out'] or aero_data['timed_out']:
self.disp_message_box("MSES Analysis Failed", message_mode='error')
self.output_area_text(
f"<font size='4'>[{self.n_analyses:2.0f}] Converged = {aero_data['converged']} | Errored out = "
f"{aero_data['errored_out']} | Timed out = {aero_data['timed_out']}</font>", mode='html')
f"[{str(self.n_analyses).zfill(2)}] MSES Converged = {aero_data['converged']} | Errored out = "
f"{aero_data['errored_out']} | Timed out = {aero_data['timed_out']}")
self.output_area_text('\n')
else:
# self.output_area_text('\n')
if "L/D" not in aero_data.keys():
aero_data["L/D"] = np.true_divide(aero_data["Cl"], aero_data["Cd"])
self.output_area_text(
f"<font size='4'>[{self.n_analyses:2.0f}] (Re = {mses_settings['REYNIN']:.3E}, "
f"[{str(self.n_analyses).zfill(2)}] MSES ( \u03b1 = {aero_data['alf']:.3f}, Re = {mses_settings['REYNIN']:.3E}, "
f"Ma = {mses_settings['MACHIN']:.3f}): "
f"Cl = {aero_data['Cl']:+7.4f} | Cd = {aero_data['Cd']:+.5f} | "
f"Cm = {aero_data['Cm']:+7.4f}</font>".replace("-", "\u2212"), mode='html')
f"Cm = {aero_data['Cm']:+7.4f} | L/D = {aero_data['L/D']:+8.4f}".replace("-", "\u2212"))
self.output_area_text('\n')
sb = self.text_area.verticalScrollBar()
sb.setValue(sb.maximum())
Expand Down Expand Up @@ -1246,17 +1253,18 @@ def shape_opt_progress_callback_fn(progress_object: object):
progress_object.exec_callback()

def shape_opt_finished_callback_fn(self):
self.output_area_text("Completed optimization.\n")
self.output_area_text("Completed aerodynamic shape optimization.\n\n")
# self.finished_optimization = True

def shape_opt_result_callback_fn(self, result_object: object):
self.output_area_text(f"Complete! Result = {result_object}\n")
pass

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):
# print(f"{opt_settings = }")
self.output_area_text(f"\nBeginning aerodynamic shape optimization with "
f"{param_dict['num_processors']} processors...\n")
Config.show_compile_hint = False
forces = []
ref_dirs = get_reference_directions("energy", param_dict['n_obj'], param_dict['n_ref_dirs'],
Expand All @@ -1272,45 +1280,14 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
if param_dict['seed'] is not None:
np.random.seed(param_dict['seed'])
random.seed(param_dict['seed'])
# tpaiga2_alg_instance = CustomGASampling(param_dict=problem.param_dict, ga_settings=ga_settings, mea=mea,
# genes=parameter_list)
# population = Population(problem.param_dict, ga_settings, generation=0,
# parents=[tpaiga2_alg_instance.generate_first_parent()],
# verbose=param_dict['verbose'], mea=mea)
# population.generate()

sampling = ConstrictedRandomSampling(n_samples=param_dict['n_offsprings'], norm_param_list=parameter_list,
max_sampling_width=param_dict['max_sampling_width'])
X_list = sampling.sample()
parents = [Chromosome(param_dict=param_dict, generation=0, population_idx=idx, mea=mea, genes=individual)
for idx, individual in enumerate(X_list)]
population = Population(param_dict=param_dict, generation=0, parents=parents,
mea=mea, verbose=param_dict['verbose'])
# population.generate_chromosomes_parallel()
# n_initial_evaluations = 0
# n_subpopulations = 0
# fully_converged_chromosomes = []
# while True: # "Do while" loop (terminate when enough of chromosomes have fully converged solutions)
# subpopulation = deepcopy(population)
# subpopulation.population = subpopulation.population[param_dict['num_processors'] * n_subpopulations:
# param_dict['num_processors'] * (
# n_subpopulations + 1)]
# subpopulation.eval_pop_fitness()
#
# for chromosome in subpopulation.population:
# if chromosome.fitness is not None:
# fully_converged_chromosomes.append(chromosome)
#
# if len(fully_converged_chromosomes) >= param_dict['population_size']:
# # Truncate the list of fully converged chromosomes to just the first <population_size> number of
# # chromosomes:
# fully_converged_chromosomes = fully_converged_chromosomes[:param_dict['population_size']]
# break
#
# n_subpopulations += 1
# n_initial_evaluations += param_dict['num_processors']
#
# if n_subpopulations * (param_dict['num_processors'] + 1) > param_dict['n_offsprings']:
# raise Exception('Ran out of chromosomes to evaluate in initial population generation')

population.eval_pop_fitness()
print(f"Finished evaluating population fitness. Continuing...")
Expand All @@ -1337,7 +1314,6 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr
print(f"{constraint.value = }")
if J is None:
J = np.array([obj.value for obj in self.objectives])
# print(f"{J = } on this pass")
else:
J = np.row_stack((J, np.array([obj.value for obj in self.objectives])))
if len(self.constraints) > 0:
Expand All @@ -1347,6 +1323,8 @@ 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 = }")

if new_X.ndim == 1:
new_X = np.array([new_X])

Expand Down Expand Up @@ -1464,6 +1442,8 @@ 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 = }")

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

for idx in range(param_dict['n_offsprings'] - len(new_X)):
Expand Down Expand Up @@ -1503,7 +1483,8 @@ def shape_optimization(self, param_dict: dict, opt_settings: dict, mea: dict, pr

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

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

if len(self.objectives) == 1:
if n_generation > 1:
Expand Down
1 change: 1 addition & 0 deletions pymead/gui/text_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def __init__(self, parent=None):
font = self.font()
# print(QFontDatabase().families())
font.setFamily("DejaVu Sans Mono")
# font.setFamily("Courier")
font.setPointSize(10)
self.setFont(font)
self.moveCursor(QTextCursor.End)
Expand Down
62 changes: 62 additions & 0 deletions pymead/optimization/opt_callback.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import typing
from abc import abstractmethod
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QFontDatabase

from pymead.core.mea import MEA
from pymead.gui.opt_airfoil_graph import OptAirfoilGraph
from pymead.gui.parallel_coords_graph import ParallelCoordsGraph
Expand All @@ -18,6 +21,65 @@ def exec_callback(self):
return


class TextCallback(OptCallback):
def __init__(self, parent, text_list: typing.List[list], completed: bool = False):
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

def generate_header(self):
t = ""
for idx, (w, n) in enumerate(zip(self.widths, self.names)):
if idx == 0:
t += "||"
t += f"{n.center(w + 2)}|"
if idx == len(self.widths) - 1:
t += "|"
header_length = len(t)
return "="*header_length + "\n" + t + "\n" + "="*header_length

def stringify_text_list(self):
t = ""
for idx, (w, v) in enumerate(zip(self.widths, self.values)):
if idx == 0:
t += "||"
if not isinstance(v, str):
v = str(v)
t += f"{v.center(w + 2)}|"
if idx == len(self.widths) - 1:
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:
# font = self.parent.text_area.font()
# print(QFontDatabase().families())
# # font.setFamily("DejaVu Sans Mono")
# font.setFamily("Courier")
# font.setPointSize(10)
# self.parent.text_area.setFont(font)
# self.parent.output_area_text(f"<head><style>body {{font-family: DejaVu Sans Mono;}}</style></head><body><p><font size='4'>&#8203;</font></p></body>", mode="html")
# self.parent.output_area_text("\n")
self.parent.output_area_text(f"{self.generate_header()}")
self.parent.output_area_text("\n")
t = f"{self.stringify_text_list()}"
self.parent.output_area_text(t)
self.parent.output_area_text("\n")
if self.completed:
print("Completed!")
self.parent.output_area_text(f"{self.generate_closer(len(t))}")
self.parent.output_area_text("\n")


class PlotAirfoilCallback(OptCallback):
def __init__(self, parent, mea: dict, X, background_color: str = 'w'):
super().__init__(parent=parent)
Expand Down
45 changes: 26 additions & 19 deletions pymead/optimization/opt_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,15 @@ def _do(self, problem, evaluator, algorithm):
decomp = get_decomposition("asf")
I = decomp.do(F, weights).argmin()
n_nds = len(algorithm.opt)
f1_best = F[I][0]
f1_min = F[:, 0].min()
f1_mean = np.mean(F[:, 0])
g1_min = G[:, 0].min()
# cv_min = CV[:, 0].min()
g1_mean = np.mean(G[:, 0])
# cv_mean = np.mean(CV[:, 0])
self.set_progress_dict({'f1_best': f1_best})
f_best = [F[I][i] for i in range(F.shape[1])]
f_min = [F[:, i].min() for i in range(F.shape[1])]
f_mean = [np.mean(F[:, i]) for i in range(F.shape[1])]
g_best, g_min, g_mean = None, None, None
if G[0] is not None:
g_best = [G[I][i] for i in range(G.shape[1])]
g_min = [G[:, i].min() for i in range(G.shape[1])]
# cv_min = CV[:, 0].min()
g_mean = [np.mean(G[:, i]) for i in range(G.shape[1])]
self.output.append("n_nds", n_nds, width=7)
self.term.do_continue(algorithm)

Expand All @@ -266,14 +267,20 @@ def _do(self, problem, evaluator, algorithm):

self.output.append("eps", eps)
self.output.append("indicator", max_from)
self.output.append("f1_best", f1_best)
# self.output.append("f2_best", f2_best)
self.output.append("f1_min", f1_min)
# self.output.append("f2_min", f2_min)
self.output.append("f1_mean", f1_mean)
# self.output.append("f2_mean", f2_mean)
self.output.append("g1_min", g1_min)
# self.output.append("cv_min", cv_min)
self.output.append("g1_mean", g1_mean)
# self.output.append("cv_mean", cv_mean)
pass
for i, f in enumerate(f_best):
self.output.append(f"f{i + 1}_best", f)
for i, f in enumerate(f_min):
self.output.append(f"f{i + 1}_min", f)
for i, f in enumerate(f_mean):
self.output.append(f"f{i + 1}_mean", f)

if G[0] is not None:
for i, g in enumerate(g_best):
self.output.append(f"g{i + 1}_best", g)
for i, g in enumerate(g_min):
self.output.append(f"g{i + 1}_min", g)
for i, g in enumerate(g_mean):
self.output.append(f"g{i + 1}_mean", g)

self.set_progress_dict(self.output.attrs)
# TODO: push this into the main textarea

0 comments on commit 32f7b30

Please sign in to comment.