Source code for pymead.optimization.objectives_and_constraints

import math


[docs] class ObjectiveConstraint:
[docs] def __init__(self, func_str: str): """ Objective or Constraint used in shape optimization. Allows for dynamic updates and equation validity checking inside the GUI. Parameters ========== func_str: str Function string used to define the Objective or Constraint from the ``aero_data`` dictionary output from the ``calc_aero_data``. """ self.func_str = func_str self.func = None self.function_dict = {} self.depends_on = {} self.value = None self.tag_matrix = None self.tag_list = None
[docs] def add_or_set_dependencies(self, dependencies: dict): """ Adds performance parameter dependencies from a Python dictionary. Parameters ========== dependencies: dict Performance parameters extracted from an airfoil system analysis used to define value of an ``Objective`` or ``Constraint``. """ for k, v in dependencies.items(): self.depends_on[k] = v
[docs] def set_func_str(self, func_str: str): """ Simple method that overwrites the ``func_str`` attribute. Parameters ========== func_str: str Function string used to define the Objective or Constraint from the ``aero_data`` dictionary output from the ``calc_aero_data``. """ self.func_str = func_str
[docs] def remove_func(self): """ Removes the ``Objective`` or ``Constraint`` function from all the relevant locations in the object. """ self.func_str = None self.func = None self.function_dict = {}
[docs] def update_function(self): """ Updates the function based on the function string and its dependencies. """ if self.func_str is None: pass else: # Convert the function string into a Python function and determine parameters present in string: math_function_list = self.parse_update_function_str() # Add any math functions detected from the func_str: for s in math_function_list: if s not in self.function_dict.keys(): if s in vars(math).keys(): self.function_dict[s] = vars(math)[s] # Add the variables the function depends on to the function_dict and detect whether the function should be # executed: execute = self.add_dependencies() # Update the function (not the result) in the function_dict if execute: try: exec(self.func, self.function_dict) return_val = self.function_dict["f"]() if not isinstance(return_val, float): raise FunctionCompileError(f"Error in function compilation output type. Required type is float," f"found type {type(return_val)}") except (SyntaxError, NameError, TypeError, IndexError): raise FunctionCompileError('Error in function compilation')
[docs] def update_value(self): """ Update the value of the ``Objective`` or ``Constraint`` using the stored function. """ if self.func_str is None: pass else: try: self.value = self.function_dict['f']() # no parameters passed as inputs (inputs all stored and updated # inside self.function_dict ) except (SyntaxError, NameError, TypeError, IndexError): raise FunctionCompileError('Error in function update')
[docs] def update(self, dependencies: dict): """ Updates the function and its value using a set of dependencies. Parameters ========== dependencies: dict Performance parameter dependencies from the airfoil system analysis. """ self.add_or_set_dependencies(dependencies) self.update_function() self.update_value()
[docs] def parse_update_function_str(self): """ Converts the function string to a function executable by Python. The special character "." is used to signal a depth increment within the airfoil system hierarchy. The special character "$" is used to define the start of a ``Param`` name. For example, the string ``"A0.Base.R_le"`` corresponds to the leading edge radius of the base parameter set of airfoil ``"A0"``. """ self.tag_matrix = [] self.func = 'def f(): return ' math_functions_to_include = [] appending = False append_new_to_math_function_list = True for ch in self.func_str: if appending: if ch.isalnum() or ch == '_': self.tag_matrix[-1][-1] += ch elif ch == '.': self.tag_matrix[-1].append('') else: appending = False if ch == '$': self.tag_matrix.append(['']) appending = True elif ch == '.' and appending: self.func += '_' else: self.func += ch if not appending and ch.isalnum(): if append_new_to_math_function_list: math_functions_to_include.append('') math_functions_to_include[-1] += ch append_new_to_math_function_list = False if not appending and not ch.isalnum(): append_new_to_math_function_list = True def concatenate_strings(lst: list): tag = '' for idx, s in enumerate(lst): tag += s if idx < len(lst) - 1: tag += '_' return tag self.tag_list = [concatenate_strings(tl) for tl in self.tag_matrix] for t in self.tag_list: self.function_dict[t] = None return math_functions_to_include
[docs] def add_dependencies(self): """Adds dependencies found in ``depends_on`` to the function dictionary for execution.""" for idx, t in enumerate(self.tag_list): if t in self.depends_on.keys(): self.function_dict[t] = self.depends_on[t] else: self.function_dict = {} self.remove_func() raise FunctionCompileError(f"Could not compile input function string: {self.func_str}") return True
[docs] class Objective(ObjectiveConstraint):
[docs] def __init__(self, func_str: str): """ Subclass of ``ObjectiveConstraint`` simply specifying the object as an Objective. Parameters ========== func_str: str Function string to pass to ``ObjectiveConstraint``'s ``__init__``. """ super().__init__(func_str)
[docs] class Constraint(ObjectiveConstraint): """ Subclass of ``ObjectiveConstraint`` simply specifying the object as an Constraint. Parameters ========== func_str: str Function string to pass to ``ObjectiveConstraint``'s ``__init__``. """
[docs] def __init__(self, func_str: str): super().__init__(func_str)
[docs] class FunctionCompileError(Exception): pass
if __name__ == '__main__': obj = Objective('$Cd') obj.update({'Cd': 0.02, 'Cl': 0.35, 'Cl_target': 0.08, 'Cl_tol': 0.005}) constraint = Constraint('abs($Cl - $Cl_target) - $Cl_tol') constraint.update({'Cd': 0.02, 'Cl': 0.35, 'Cl_target': 0.3455, 'Cl_tol': 0.005}) pass