From a6ce018ae833372e68f554c84f67d9abe4862f2b Mon Sep 17 00:00:00 2001 From: joanibal Date: Wed, 20 Nov 2019 00:14:53 -0500 Subject: [PATCH 01/21] mod for nacelle opt --- python/BaseSolver.py | 81 +++++++++---------- python/pyAero_problem.py | 166 +++++++++++++++++++++------------------ 2 files changed, 126 insertions(+), 121 deletions(-) diff --git a/python/BaseSolver.py b/python/BaseSolver.py index 3202e22..95ba78e 100644 --- a/python/BaseSolver.py +++ b/python/BaseSolver.py @@ -1,4 +1,7 @@ from __future__ import print_function +import sys +import os +from pprint import pprint as pp #!/usr/local/bin/python ''' BaseSolver @@ -22,21 +25,16 @@ __version__ = '$Revision: $' -''' -ToDo: - - -''' # ============================================================================= # Standard Python modules # ============================================================================= -import os, sys -from pprint import pprint as pp # ============================================================================= # Misc Definitions # ============================================================================= + class CaseInsensitiveDict(dict): def __setitem__(self, key, value): super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) @@ -47,16 +45,18 @@ def __getitem__(self, key): def __contains__(self, key): return super(CaseInsensitiveDict, self).__contains__(key.lower()) + class Error(Exception): """ Format the error message in a box to make it clear this - was a expliclty raised exception. + was a explicitly raised exception. """ + def __init__(self, message): msg = '\n+'+'-'*78+'+'+'\n' + '| BaseSolver Error: ' i = 19 for word in message.split(): - if len(word) + i + 1 > 78: # Finish line and start new one + if len(word) + i + 1 > 78: # Finish line and start new one msg += ' '*(78-i)+'|\n| ' + word + ' ' i = 1 + len(word)+1 else: @@ -71,27 +71,26 @@ def __init__(self, message): # BaseSolver Class # ============================================================================= class BaseSolver(object): - + ''' Abstract Class for a basic Solver Object ''' - + def __init__(self, name, category={}, def_options={}, **kwargs): - ''' - StructSolver Class Initialization - - Documentation last updated: + Solver Class Initialization + + Documentation last updated: ''' - - # + + # self.name = name self.category = category self.options = CaseInsensitiveDict() self.defaultOptions = def_options self.solverCreated = False self.imOptions = {} - + # Initialize Options for key in self.defaultOptions: self.setOption(key, self.defaultOptions[key][1]) @@ -101,16 +100,14 @@ def __init__(self, name, category={}, def_options={}, **kwargs): self.setOption(key, koptions[key]) self.solverCreated = True - + def __call__(self, *args, **kwargs): - ''' Run Analyzer (Calling Routine) - - Documentation last updated: + + Documentation last updated: ''' - - + # Checks pass @@ -122,22 +119,22 @@ def setOption(self, name, value): ---------- name : str Name of option to set. Not case sensitive - value : varries - Value to set. Type is checked for consistency. - + value : varies + Value to set. Type is checked for consistency. + """ name = name.lower() - try: + try: self.defaultOptions[name] except KeyError: - Error("Option \'%-30s\' is not a valid %s option."%( + Error("Option \'%-30s\' is not a valid %s option." % ( name, self.name)) # Make sure we are not trying to change an immutable option if # we are not allowed to. if self.solverCreated and name in self.imOptions: raise Error("Option '%-35s' cannot be modified after the solver " - "is created."%name) + "is created." % name) # Now we know the option exists, lets check if the type is ok: if isinstance(value, self.defaultOptions[name][0]): @@ -146,9 +143,9 @@ def setOption(self, name, value): else: raise Error("Datatype for Option %-35s was not valid \n " "Expected data type is %-47s \n " - "Received data type is %-47s"% ( + "Received data type is %-47s" % ( name, self.defaultOptions[name][0], type(value))) - + def getOption(self, name): """ Default implementation of getOption() @@ -160,23 +157,22 @@ def getOption(self, name): Returns ------- - value : varries - Return the curent value of the option. + value : varies + Return the current value of the option. """ if name.lower() in self.defaultOptions: return self.options[name.lower()][1] else: - raise Error('%s is not a valid option name.'% name) + raise Error('%s is not a valid option name.' % name) def printCurrentOptions(self): - """ Prints a nicely formatted dictionary of all the current solver options to the stdout on the root processor""" if self.comm.rank == 0: print('+---------------------------------------+') - print('| All %s Options: |'%self.name) + print('| All %s Options: |' % self.name) print('+---------------------------------------+') # Need to assemble a temporary dictionary tmpDict = {} @@ -185,14 +181,13 @@ def printCurrentOptions(self): pp(tmpDict) def printModifiedOptions(self): - """ Prints a nicely formatted dictionary of all the current solver options that have been modified from the defaults to the root processor""" if self.comm.rank == 0: print('+---------------------------------------+') - print('| All Modified %s Options: |'%self.name) + print('| All Modified %s Options: |' % self.name) print('+---------------------------------------+') # Need to assemble a temporary dictionary tmpDict = {} @@ -201,14 +196,14 @@ def printModifiedOptions(self): tmpDict[key] = self.getOption(key) pp(tmpDict) -#============================================================================== + +# ============================================================================== # Optimizer Test -#============================================================================== +# ============================================================================== if __name__ == '__main__': - + print('Testing ...') - + # Test Optimizer azr = BaseSolver('Test') dir(azr) - diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index 5b50971..94b3613 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -19,6 +19,7 @@ from .ICAOAtmosphere import ICAOAtmosphere from .FluidProperties import FluidProperties + class CaseInsensitiveDict(dict): def __setitem__(self, key, value): super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) @@ -26,25 +27,28 @@ def __setitem__(self, key, value): def __getitem__(self, key): return super(CaseInsensitiveDict, self).__getitem__(key.lower()) + class Error(Exception): """ Format the error message in a box to make it clear this was a expliclty raised exception. """ + def __init__(self, message): - msg = '\n+'+'-'*78+'+'+'\n' + '| AeroProblem Error: ' + msg = '\n+' + '-' * 78 + '+' + '\n' + '| AeroProblem Error: ' i = 20 for word in message.split(): - if len(word) + i + 1 > 78: # Finish line and start new one - msg += ' '*(78-i)+'|\n| ' + word + ' ' - i = 1 + len(word)+1 + if len(word) + i + 1 > 78: # Finish line and start new one + msg += ' ' * (78 - i) + '|\n| ' + word + ' ' + i = 1 + len(word) + 1 else: msg += word + ' ' - i += len(word)+1 - msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n' + i += len(word) + 1 + msg += ' ' * (78 - i) + '|\n' + '+' + '-' * 78 + '+' + '\n' print(msg) Exception.__init__(self) + class AeroProblem(FluidProperties): """ The main purpose of this class is to represent all relevant @@ -204,11 +208,10 @@ class AeroProblem(FluidProperties): Set the reference axis for non-x/y/z based moment calculations R : float - The gas constant. By default we use air (287.055 J / kg / K). + The gas constant. By defalut we use air. R=287.05 - englishUnits : bool. Default is False. - Flag to use all English units: pounds, feet, Rankine etc. If false, SI - units are used. + englishUnits : bool + Flag to use all English units: pounds, feet, Rankine etc. solverOptions : dict A set of solver specific options that temprorily overide the solver's @@ -234,6 +237,7 @@ class AeroProblem(FluidProperties): >>> ap = AeroProblem('m6_tunnel', mach=0.8395, reynolds=11.72e6, reynoldsLenght=0.64607, \ areaRef=0.772893541, chordRef=0.64607, xRef=0.0, zRef=0.0, alpha=3.06, T=255.56) """ + def __init__(self, name, **kwargs): # Set basic fluid properties @@ -245,11 +249,11 @@ def __init__(self, name, **kwargs): # These are the parameters that can be simply set directly in # the class. paras = set(('alpha', 'beta', 'areaRef', 'chordRef', 'spanRef', - 'xRef', 'yRef', 'zRef','xRot', 'yRot', 'zRot', - 'phat', 'qhat', 'rhat', 'momentAxis', - 'degreePol', 'coefPol', 'degreeFourier', 'omegaFourier', - 'cosCoefFourier', 'sinCoefFourier', - 'machRef', 'machGrid')) + 'xRef', 'yRef', 'zRef', 'xRot', 'yRot', 'zRot', + 'phat', 'qhat', 'rhat', 'momentAxis', + 'degreePol', 'coefPol', 'degreeFourier', 'omegaFourier', + 'cosCoefFourier', 'sinCoefFourier', + 'machRef', 'machGrid')) # By default everything is None for para in paras: @@ -264,7 +268,7 @@ def __init__(self, name, **kwargs): self.solverOptions = CaseInsensitiveDict({}) if 'solverOptions' in kwargs: for key in kwargs['solverOptions']: - self.solverOptions[key] = kwargs['solverOptions'][key] + self.solverOptions[key] = kwargs['solverOptions'][key] # Any matching key from kwargs that is in 'paras' for key in kwargs: @@ -282,7 +286,7 @@ def __init__(self, name, **kwargs): # these are the possible input values possibleInputStates = set(['mach', 'V', 'P', 'T', 'rho', 'altitude', 'reynolds', - 'reynoldsLength']) + 'reynoldsLength']) # turn the kwargs into a set keys = set(kwargs.keys()) @@ -291,28 +295,27 @@ def __init__(self, name, **kwargs): self.inputs = {} for key in keys: if key in possibleInputStates: - self.inputs[key]= kwargs[key] + self.inputs[key] = kwargs[key] # full list of states in the class self.fullState = set(['mach', 'V', 'P', 'T', 'rho', 'mu', 'nu', 'a', - 'q', 'altitude', 're','reynolds','reynoldsLength']) + 'q', 'altitude', 're', 'reynolds', 'reynoldsLength']) # now call the routine to setup the states self._setStates(self.inputs) - # Specify the set of possible design variables: self.allVarFuncs = ['alpha', 'beta', 'areaRef', 'chordRef', 'spanRef', - 'xRef', 'yRef', 'zRef', 'xRot', 'yRot', 'zRot', 'momentAxis', - 'phat', 'qhat', 'rhat', 'mach', 'altitude', 'P', 'T', - 'reynolds','reynoldsLength'] + 'xRef', 'yRef', 'zRef', 'xRot', 'yRot', 'zRot', 'momentAxis', + 'phat', 'qhat', 'rhat', 'mach', 'altitude', 'P', 'T', + 'reynolds', 'reynoldsLength'] self.possibleDVs = set() for var in self.allVarFuncs: if getattr(self, var) is not None: self.possibleDVs.add(var) - BCVarFuncs = ['Pressure', 'PressureStagnation', 'TemperatureStagnation', 'Thrust'] + BCVarFuncs = [ 'Pressure', 'PressureStagnation','Temperature', 'TemperatureStagnation', 'Thrust'] self.possibleBCDVs = set(BCVarFuncs) # Now determine the possible functions. Any possible design @@ -335,7 +338,7 @@ def __init__(self, name, **kwargs): # vars are keyed by (bcVarName, Family) self.bcVarData = {} - def _setStates(self,inputDict): + def _setStates(self, inputDict): ''' Take in a dictionary and set up the full set of states. ''' @@ -357,39 +360,39 @@ def _setStates(self,inputDict): else: validKeys = '' for vkey in self.inputs: - validKeys += vkey+', ' + validKeys += vkey + ', ' raise Error('Invalid input parameter: %s . Only values initially specifed' - ' as inputs may be modifed. valid inputs include: %s'%(key,validKeys)) + ' as inputs may be modifed. valid inputs include: %s' % (key, validKeys)) # now we know our inputs are valid. update self.Input and update states for key in inputDict: - self.inputs[key]=inputDict[key] + self.inputs[key] = inputDict[key] if set(('mach', 'T', 'P')) <= inKeys: self.__dict__['mach'] = self.inputs['mach'] self.__dict__['T'] = self.inputs['T'] self.__dict__['P'] = self.inputs['P'] - self.__dict__['rho'] = self.P/(self.R*self.T) + self.__dict__['rho'] = self.P / (self.R * self.T) # now calculate remaining states self._updateFromM() elif set(('mach', 'T', 'rho')) <= inKeys: self.__dict__['mach'] = self.inputs['mach'] self.__dict__['T'] = self.inputs['T'] self.__dict__['rho'] = self.inputs['rho'] - self.__dict__['P'] = self.rho*self.R*self.T + self.__dict__['P'] = self.rho * self.R * self.T # now calculate remaining states self._updateFromM() elif set(('mach', 'P', 'rho')) <= inKeys: self.__dict__['mach'] = self.inputs['mach'] self.__dict__['rho'] = self.inputs['rho'] self.__dict__['P'] = self.inputs['P'] - self.__dict__['T'] = self.P /(self.rho*self.R) + self.__dict__['T'] = self.P / (self.rho * self.R) # now calculate remaining states self._updateFromM() elif set(('mach', 'reynolds', 'reynoldsLength', 'T')) <= inKeys: self.__dict__['mach'] = self.inputs['mach'] self.__dict__['T'] = self.inputs['T'] - self.__dict__['re'] = self.inputs['reynolds']/self.inputs['reynoldsLength'] + self.__dict__['re'] = self.inputs['reynolds'] / self.inputs['reynoldsLength'] self.__dict__['reynolds'] = self.inputs['reynolds'] self.__dict__['reynoldsLength'] = self.inputs['reynoldsLength'] # now calculate remaining states @@ -397,7 +400,7 @@ def _setStates(self,inputDict): elif set(('V', 'reynolds', 'reynoldsLength', 'T')) <= inKeys: self.__dict__['V'] = self.inputs['V'] self.__dict__['T'] = self.inputs['T'] - self.__dict__['re'] = self.inputs['reynolds']/self.inputs['reynoldsLength'] + self.__dict__['re'] = self.inputs['reynolds'] / self.inputs['reynoldsLength'] self.__dict__['reynolds'] = self.inputs['reynolds'] self.__dict__['reynoldsLength'] = self.inputs['reynoldsLength'] # now calculate remaining states @@ -408,28 +411,28 @@ def _setStates(self,inputDict): P, T = self.atm(self.inputs['altitude']) self.__dict__['T'] = T self.__dict__['P'] = P - self.__dict__['rho'] = self.P/(self.R*self.T) + self.__dict__['rho'] = self.P / (self.R * self.T) self._updateFromM() elif set(('V', 'rho', 'T')) <= inKeys: self.__dict__['V'] = self.inputs['V'] self.__dict__['rho'] = self.inputs['rho'] self.__dict__['T'] = self.inputs['T'] # calculate pressure - self.__dict__['P'] = self.rho*self.R*self.T + self.__dict__['P'] = self.rho * self.R * self.T self._updateFromV() elif set(('V', 'rho', 'P')) <= inKeys: self.__dict__['V'] = self.inputs['V'] self.__dict__['rho'] = self.inputs['rho'] self.__dict__['P'] = self.inputs['P'] - #start by calculating the T - self.__dict__['T'] = self.P /(self.rho*self.R) + # start by calculating the T + self.__dict__['T'] = self.P / (self.rho * self.R) self._updateFromV() elif set(('V', 'T', 'P')) <= inKeys: self.__dict__['V'] = self.inputs['V'] self.__dict__['T'] = self.inputs['T'] self.__dict__['P'] = self.inputs['P'] - #start by calculating the T - self.__dict__['rho'] = self.P/(self.R*self.T) + # start by calculating the T + self.__dict__['rho'] = self.P / (self.R * self.T) self._updateFromV() else: raise Error('There was not sufficient information to form ' @@ -443,6 +446,7 @@ def setBCVar(self, varName, value, familyName): """ self.bcVarData[varName, familyName] = value + print('update bc', value) def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, name=None, offset=0.0, dvOffset=0.0, addToPyOpt=True, family=None, @@ -516,7 +520,7 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, """ if (key not in self.allVarFuncs) and (key not in self.possibleBCDVs): - raise ValueError('%s is not a valid design variable'%key) + raise ValueError('%s is not a valid design variable' % key) # First check if we are allowed to add the DV: elif (key not in self.possibleDVs) and (key in self.allVarFuncs): @@ -524,13 +528,13 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, "be specified when the aeroProblem class is created. " "For example, if you want alpha as a design variable " "(...,alpha=value, ...) must be given. The list of " - "possible DVs are: %s."% (key, repr(self.possibleDVs))) + "possible DVs are: %s." % (key, repr(self.possibleDVs))) if key in self.possibleBCDVs: if family is None: raise Error("The family must be given for BC design variables") if name is None: - dvName = '%s_%s_%s'%(key, family, self.name) + dvName = '%s_%s_%s' % (key, family, self.name) else: dvName = name @@ -540,7 +544,7 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, value = self.bcVarData[key, family] else: if name is None: - dvName = key + '_%s'% self.name + dvName = key + '_%s' % self.name else: dvName = name @@ -583,10 +587,10 @@ def setDesignVars(self, x): else: self.bcVarData[key, family] = value - try: # To set in the DV as well if the DV exists: + try: # To set in the DV as well if the DV exists: self.DVs[dvName].value = x[dvName] except: - pass # DV doesn't exist + pass # DV doesn't exist def addVariablesPyOpt(self, optProb): """ @@ -601,19 +605,25 @@ def addVariablesPyOpt(self, optProb): for dvName in self.DVs: dv = self.DVs[dvName] if dv.addToPyOpt: - optProb.addVar(dvName, 'c', value=dv.value, lower=dv.lower, - upper=dv.upper, scale=dv.scale, - offset=dv.dvOffset, units=dv.units) + if type(dv.value) == numpy.ndarray: + optProb.addVarGroup(dvName, dv.value.size, 'c', value=dv.value, lower=dv.lower, + upper=dv.upper, scale=dv.scale, + offset=dv.dvOffset, units=dv.units) + else: + optProb.addVar(dvName, 'c', value=dv.value, lower=dv.lower, + upper=dv.upper, scale=dv.scale, + offset=dv.dvOffset, units=dv.units) + def __getitem__(self, key): return self.funcNames[key] def __str__(self): - for key,val in self.__dict__.items(): - print ("{0:20} : {1:<16}".format(key,val)) - - + output_str = '' + for key, val in self.__dict__.items(): + output_str += "{0:20} : {1:<16}\n".format(key, val) + return output_str def evalFunctions(self, funcs, evalFuncs, ignoreMissing=False): """ Evaluate the desired aerodynamic functions. It may seem @@ -653,13 +663,13 @@ def evalFunctions(self, funcs, evalFuncs, ignoreMissing=False): # All the functions are ok: for f in evalFuncs: # Save the key into funcNames - key = self.name + '_%s'% f + key = self.name + '_%s' % f self.funcNames[f] = key funcs[key] = getattr(self, f) else: if not ignoreMissing: raise Error("One of the functions in 'evalFunctionsSens' was " - "not valid. The valid list of functions is: %s."% ( + "not valid. The valid list of functions is: %s." % ( repr(self.possibleFunctions))) def evalFunctionsSens(self, funcsSens, evalFuncs, ignoreMissing=True): @@ -685,7 +695,7 @@ def evalFunctionsSens(self, funcsSens, evalFuncs, ignoreMissing=True): else: if not ignoreMissing: raise Error("One of the functions in 'evalFunctionsSens' was " - "not valid. The valid list of functions is: %s."% ( + "not valid. The valid list of functions is: %s." % ( repr(self.possibleFunctions))) def _set_aeroDV_val(self, key, value): @@ -701,7 +711,7 @@ def mach(self): @mach.setter def mach(self, value): - self._setStates({'mach':value}) + self._setStates({'mach': value}) self._set_aeroDV_val('mach', value) @property @@ -710,7 +720,7 @@ def T(self): @T.setter def T(self, value): - self._setStates({'T':value}) + self._setStates({'T': value}) self._set_aeroDV_val('T', value) @property @@ -719,7 +729,7 @@ def P(self): @P.setter def P(self, value): - self._setStates({'P':value}) + self._setStates({'P': value}) self._set_aeroDV_val('P', value) @property @@ -728,7 +738,7 @@ def rho(self): @rho.setter def rho(self, value): - self._setStates({'rho':value}) + self._setStates({'rho': value}) self._set_aeroDV_val('rho', value) @property @@ -737,7 +747,7 @@ def re(self): @re.setter def re(self, value): - self._setStates({'re':value}) + self._setStates({'re': value}) self._set_aeroDV_val('re', value) @property @@ -746,7 +756,7 @@ def reynolds(self): @reynolds.setter def reynolds(self, value): - self._setStates({'reynolds':value}) + self._setStates({'reynolds': value}) self._set_aeroDV_val('reynolds', value) @property @@ -755,7 +765,7 @@ def reynoldsLength(self): @reynoldsLength.setter def reynoldsLength(self, value): - self._setStates({'reynoldsLength':value}) + self._setStates({'reynoldsLength': value}) self._set_aeroDV_val('reynoldsLength', value) @property @@ -764,7 +774,7 @@ def altitude(self): @altitude.setter def altitude(self, value): - self._setStates({'altitude':value}) + self._setStates({'altitude': value}) self._set_aeroDV_val('altitude', value) # def _update(self): @@ -816,7 +826,7 @@ def _updateFromRe(self): update the full set of states from M,T,P ''' # calculate the speed of sound - self.a = numpy.sqrt(self.gamma*self.R*self.T) + self.a = numpy.sqrt(self.gamma * self.R * self.T) # Update the dynamic viscosity based on T using Sutherland's Law self.updateViscosity(self.T) @@ -825,25 +835,25 @@ def _updateFromRe(self): if self.V is None: self.V = self.mach * self.a else: - self.__dict__['mach'] = self.V/self.a + self.__dict__['mach'] = self.V / self.a # calculate density - self.__dict__['rho'] = self.re*self.mu/self.V + self.__dict__['rho'] = self.re * self.mu / self.V # calculate pressure - self.__dict__['P'] = self.rho*self.R*self.T + self.__dict__['P'] = self.rho * self.R * self.T # calculate kinematic viscosity self.nu = self.mu / self.rho # calculate dynamic pressure - self.q = 0.5*self.rho*self.V**2 + self.q = 0.5 * self.rho * self.V**2 def _updateFromM(self): ''' update the full set of states from M,T,P, Rho ''' # calculate the speed of sound - self.a = numpy.sqrt(self.gamma*self.R*self.T) + self.a = numpy.sqrt(self.gamma * self.R * self.T) # Update the dynamic viscosity based on T using Sutherland's Law self.updateViscosity(self.T) @@ -852,20 +862,20 @@ def _updateFromM(self): self.V = self.mach * self.a # calulate reynolds per length - self.__dict__['re'] = self.rho*self.V/self.mu + self.__dict__['re'] = self.rho * self.V / self.mu # calculate kinematic viscosity self.nu = self.mu / self.rho # calculate dynamic pressure - self.q = 0.5*self.rho*self.V**2 + self.q = 0.5 * self.rho * self.V**2 def _updateFromV(self): ''' update the full set of states from V,T,P, Rho ''' # calculate the speed of sound - self.a = numpy.sqrt(self.gamma*self.R*self.T) + self.a = numpy.sqrt(self.gamma * self.R * self.T) # Update the dynamic viscosity based on T using Sutherland's Law self.updateViscosity(self.T) @@ -874,14 +884,13 @@ def _updateFromV(self): self.nu = self.mu / self.rho # calculate dynamic pressure - self.q = 0.5*self.rho*self.V**2 + self.q = 0.5 * self.rho * self.V**2 # calculate Mach Number - self.__dict__['mach'] = self.V/self.a + self.__dict__['mach'] = self.V / self.a # calulate reynolds per length - self.__dict__['re'] = self.rho*self.V/self.mu - + self.__dict__['re'] = self.rho * self.V / self.mu def _getDVSens(self, func): """ @@ -889,13 +898,14 @@ def _getDVSens(self, func): evalFuncs, wrt the design variable key 'key' """ rDict = {} - h = 1e-40j; hr = 1e-40 + h = 1e-40j + hr = 1e-40 for dvName in self.DVs: key = self.DVs[dvName].key family = self.DVs[dvName].family if family is None: setattr(self, key, getattr(self, key) + h) - rDict[dvName] = numpy.imag(self.__dict__[func])/hr + rDict[dvName] = numpy.imag(self.__dict__[func]) / hr setattr(self, key, numpy.real(getattr(self, key))) return rDict From ff3d7200e3ebd3494f2cf4ac1c372844fd2164ad Mon Sep 17 00:00:00 2001 From: joanibal Date: Wed, 26 Feb 2020 12:05:06 -0500 Subject: [PATCH 02/21] seperated bcdata and actuator data --- python/pyAero_problem.py | 131 ++++++++++++++++++++++++++++++++------- python/pyAero_solver.py | 61 ++++++++++++++---- 2 files changed, 156 insertions(+), 36 deletions(-) diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index 0c51461..c9e41dc 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -318,9 +318,17 @@ def __init__(self, name, **kwargs): if getattr(self, var) is not None: self.possibleDVs.add(var) - BCVarFuncs = [ 'Pressure', 'PressureStagnation','Temperature', 'TemperatureStagnation', 'Thrust'] + BCVarFuncs = \ + ['Temperature', 'Pressure', 'Density', \ + 'TemperatureStagnation', 'PressureStagnation', 'DensityStagnation', \ + 'VelocityX', 'VelocityY', 'VelocityZ', 'VelocityR', 'VelocityTheta', \ + 'VelocityUnitVectorX', 'VelocityUnitVectorY', 'VelocityUnitVectorZ', 'VelocityUnitVectorR', 'VelocityUnitVectorTheta', \ + 'VelocityAngleX', 'VelocityAngleY', 'VelocityAngleZ'] self.possibleBCDVs = set(BCVarFuncs) + actuatorFuncs = ['Thrust', 'Torque'] + self.possibleActuatorDVs = set(actuatorFuncs) + # Now determine the possible functions. Any possible design # variable CAN also be a function (pass through) self.possibleFunctions = set(self.possibleDVs) @@ -339,7 +347,8 @@ def __init__(self, name, **kwargs): # Storage of BC varible values # vars are keyed by (bcVarName, Family) - self.bcVarData = {} + self.BCData = {} + self.actuatorData = {} def _setStates(self, inputDict): ''' @@ -443,16 +452,29 @@ def _setStates(self, inputDict): 'in for pyAero_problem.py for information on how ' 'to correctly specify the aerodynamic state') - def setBCVar(self, varName, value, familyName): + def setBCVar(self, varName, value, groupName): """ set the value of a BC variable on a specific variable """ + if not groupName in self.BCData.keys(): + self.BCData[groupName] = {} + + self.BCData[groupName][varName] = value + + def getBCData(self): + return self.BCData - self.bcVarData[varName, familyName] = value - print('update bc', value) + def setActuatorVar(self, varName, value, groupName): + if not groupName in self.BCData.keys(): + self.actuatorData[groupName] = {} + + self.actuatorData[groupName][varName] = value + + def getActuatorData(self): + return self.actuatorData def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, - name=None, offset=0.0, dvOffset=0.0, addToPyOpt=True, family=None, + name=None, offset=0.0, dvOffset=0.0, addToPyOpt=True, familyGroup=None, units=None): """ Add one of the class attributes as an 'aerodynamic' design @@ -522,7 +544,8 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, >>> ap.addDV('alpha', value=2.5, lower=0.0, upper=10.0, scale=0.1) """ - if (key not in self.allVarFuncs) and (key not in self.possibleBCDVs): + if (key not in self.allVarFuncs) and (key not in self.possibleBCDVs) and \ + (key not in self.possibleActuatorDVs): raise ValueError('%s is not a valid design variable' % key) # First check if we are allowed to add the DV: @@ -532,19 +555,71 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, "For example, if you want alpha as a design variable " "(...,alpha=value, ...) must be given. The list of " "possible DVs are: %s." % (key, repr(self.possibleDVs))) - if key in self.possibleBCDVs: - if family is None: - raise Error("The family must be given for BC design variables") + + + if key in self.possibleBCDVs or self.possibleActuatorDVs: + if familyGroup is None: + raise Error("The familyGroup must be given for BC or actuator\ + design variables") if name is None: - dvName = '%s_%s_%s' % (key, family, self.name) + dvName = '%s_%s_%s' % (key, familyGroup, self.name) else: dvName = name if value is None: - if (key, family) not in self.bcVarData: - raise Error("The value must be given or set using the setBCVar routine") - value = self.bcVarData[key, family] + if key in self.possibleBCDVs: + try: + value = self.BCData[familyGroup][key] + except KeyError : + raise Error("The value must be given or set using the setBCVar routine") + else: + try: + value = self.actuatorData[familyGroup][key] + except KeyError : + raise Error("The value must be given or set using the setActuatorVar routine") + + # the value of the BCData[familyGroup][key] maybe a dictionary of data for each patch + # to accomadate this we will add a variable for each entry in the dictionary + if isinstance(value, dict) and key in self.possibleBCDVs: + + # use a little bit o recursion + for dict_key in value: + dict_key_name = str(dict_key).replace(" ", "") + dict_dvName = '%s_%s' % (dvName , dict_key_name) + + if isinstance(lower, dict): + lower_val = lower[dict_key] + else: + lower_val = lower + if isinstance(upper, dict): + upper_val = upper[dict_key] + else: + upper_val = upper + if isinstance(scale, dict): + scale_val = scale[dict_key] + else: + scale_val = scale + if isinstance(offset, dict): + offset_val = offset[dict_key] + else: + offset_val = offset + if isinstance(dvOffset, dict): + dvOffset_val = dvOffset[dict_key] + else: + dvOffset_val = dvOffset + + + # self.addDV(key, , lower_val, upper_val, scale_val, dvName, + # offset_val, dvOffset_val, addToPyOpt, familyGroup, + # units) + + self.DVs[dict_dvName] = aeroDV(key, value[dict_key], lower_val, upper_val, scale_val, offset_val, + dvOffset_val, addToPyOpt, familyGroup, units) + self.DVs[dict_dvName].dict_key = dict_key + + return + else: if name is None: dvName = key + '_%s' % self.name @@ -553,10 +628,10 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, if value is None: value = getattr(self, key) - family = None + familyGroup = None self.DVs[dvName] = aeroDV(key, value, lower, upper, scale, offset, - dvOffset, addToPyOpt, family, units) + dvOffset, addToPyOpt, familyGroup, units) def updateInternalDVs(self): """ @@ -585,15 +660,25 @@ def setDesignVars(self, x): key = self.DVs[dvName].key family = self.DVs[dvName].family value = x[dvName] + self.DVs[dvName].offset - if family is None: - setattr(self, key, value) + if key in self.possibleBCDVs: + + if hasattr(self.DVs[dvName], 'dict_key'): + dict_key = self.DVs[dvName].dict_key + self.BCData[family][key][dict_key] = value + else: + self.BCData[family][key] = value + elif key in self.possibleActuatorDVs: + self.actuatorData[family][key] = value else: - self.bcVarData[key, family] = value + setattr(self, key, value) + + self.DVs[dvName].value = x[dvName] + # try: # To set in the DV as well if the DV exists: + # except: + # # DV doesn't exist + # warnings.warn("Design variable, {}, not present in self.DVs,\ + # but was give".format(dvName)) - try: # To set in the DV as well if the DV exists: - self.DVs[dvName].value = x[dvName] - except: - pass # DV doesn't exist def addVariablesPyOpt(self, optProb): """ diff --git a/python/pyAero_solver.py b/python/pyAero_solver.py index dfee712..c85dbb2 100644 --- a/python/pyAero_solver.py +++ b/python/pyAero_solver.py @@ -94,6 +94,7 @@ def __init__(self, name, category={}, def_options={}, informs={}, options={}, ** """ self.families = CaseInsensitiveDict() + self.familyGroups = CaseInsensitiveDict() # Setup option info BaseSolver.__init__(self,name, category=category,def_options=def_options, options=options,**kwargs) @@ -322,10 +323,25 @@ def resetFlow(self): pass - def addFamilyGroup(self, groupName, families): + def addFamilyGroup(self, groupName, groups): """Add a custom grouping of families called groupName. The groupName - must be distinct from the existing families. All families must - in the 'families' list must be present in the CGNS file. + must be distinct from the existing family groups names. + # All group in the 'families' list must be present in the CGNS file. + + + + ---------------------------------- + self.families | 'inlet' 'outlet' 'nozzle_wall' | + (from surface definition) | [1] [2] [3] | + ---------------------------------- + + ----------------------------------------------------------------- + self.familyGroups | 'inlet' 'outlet' 'nozzle_wall' 'allwalls' 'allsurfaces' | + (collection of | [1] [2] [3] [3] [1, 2, 3] | + families) ----------------------------------------------------------------- + + all familes are family groups since each is a collection of a single + family (itself) Parameters ---------- @@ -336,27 +352,28 @@ def addFamilyGroup(self, groupName, families): """ # Do some error checking - if groupName in self.families: + if groupName in self.familyGroups: raise Error("The specified groupName '%s' already exists in the " "cgns file or has already been added."%groupName) # We can actually allow for nested groups. That is, an entry # in families may already be a group added in a previous call. indices = [] - for fam in families: - if fam.lower() not in self.families: + for group in groups: + if group.lower() not in self.familyGroups: raise Error("The specified family '%s' for group '%s', does " "not exist in the cgns file or has " "not already been added. The current list of " - "families (original and grouped) is: %s"%( - fam, groupName, repr(self.families.keys()))) + "families is: %s and the current list of family" + "groups is: %s"%(group, groupName, repr(self.families.keys()), + repr(self.familyGroups.keys())) ) - indices.extend(self.families[fam]) + indices.extend(self.familyGroups[group]) # It is very important that the list of families is sorted # becuase in fortran we always use a binary search to check if # a famID is in the list. - self.families[groupName] = sorted(numpy.unique(indices)) + self.familyGroups[groupName] = sorted(numpy.unique(indices)) def getSurfaceCoordinates(self,group_name): """ @@ -523,6 +540,15 @@ def printFamilyList(self): """ from pprint import pprint pprint(self.families) + + def printFamilyGroupList(self): + """ + Print a nicely formatted dictionary of the family names + """ + from pprint import pprint + pprint(self.familyGroups) + + # -------------------------- # Private Utility functions # -------------------------- @@ -533,10 +559,19 @@ def _getFamilyList(self, groupName): if groupName is None: groupName = self.allFamilies - if groupName not in self.families: + if groupName not in self.familyGroups: raise Error("'%s' is not a family in the CGNS file or has not been added" " as a combination of families"%groupName) - return self.families[groupName] + return self.familyGroups[groupName] - + def _getFamiliesInFamilyGroup(self, groupName): + famlist = self._getFamilyList(groupName) + + groupFamilies = [] + for famID in famlist: + for family in self.families: + if famID == self.families[family]: + groupFamilies.append(family) + + return groupFamilies \ No newline at end of file From b51da62d323b429e67b31cfb75346c59e8793755 Mon Sep 17 00:00:00 2001 From: joanibal Date: Tue, 3 Mar 2020 14:27:48 -0500 Subject: [PATCH 03/21] bug fix --- python/pyAero_problem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index c9e41dc..10e36b8 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -556,8 +556,7 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, "(...,alpha=value, ...) must be given. The list of " "possible DVs are: %s." % (key, repr(self.possibleDVs))) - - if key in self.possibleBCDVs or self.possibleActuatorDVs: + if key in self.possibleBCDVs or key in self.possibleActuatorDVs: if familyGroup is None: raise Error("The familyGroup must be given for BC or actuator\ design variables") From b5c2016cfde049c1059d1c32e3d89f720aa5c631 Mon Sep 17 00:00:00 2001 From: joanibal Date: Tue, 31 Mar 2020 11:16:41 -0400 Subject: [PATCH 04/21] bug fix --- python/pyAero_problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index 10e36b8..08f474c 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -465,7 +465,7 @@ def getBCData(self): return self.BCData def setActuatorVar(self, varName, value, groupName): - if not groupName in self.BCData.keys(): + if not groupName in self.actuatorData.keys(): self.actuatorData[groupName] = {} self.actuatorData[groupName][varName] = value From 4d472317aced445058ffbc7124a981fbfc401d4b Mon Sep 17 00:00:00 2001 From: joanibal Date: Thu, 9 Apr 2020 13:25:20 -0400 Subject: [PATCH 05/21] uses reference files --- python/BaseRegTest.py | 147 ++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 57 deletions(-) diff --git a/python/BaseRegTest.py b/python/BaseRegTest.py index c186107..8a5a7c0 100644 --- a/python/BaseRegTest.py +++ b/python/BaseRegTest.py @@ -1,18 +1,20 @@ from __future__ import print_function from mpi4py import MPI -import pickle import numpy import os +import pprint class BaseRegTest(object): - def __init__(self, ref_file, train=False, comm=None, check_arch=False): - self.ref_file = ref_file + def __init__(self, ref , train=False, comm=None, check_arch=False): + # self.ref_file = ref_file + + self.setRef(ref) self.train = train - if not self.train: - # We need to check here that the reference file exists, otherwise - # it will hang when it tries to open it on the root proc. - assert(os.path.isfile(self.ref_file)) + # if not self.train: + # # We need to check here that the reference file exists, otherwise + # # it will hang when it tries to open it on the root proc. + # assert(os.path.isfile(self.ref_file)) if comm is not None: self.comm = comm @@ -20,19 +22,21 @@ def __init__(self, ref_file, train=False, comm=None, check_arch=False): self.comm = MPI.COMM_WORLD self.rank = self.comm.rank - if self.rank == 0: - self.counter = 0 + # if self.rank == 0: + # self.counter = 0 + + # if self.train: + # self.db = [] + # else: + # # with open(self.ref_file, 'rb') as file_handle: + # # self.db = pickle.load(file_handle) + # with open(self.ref_file, 'r') as file_handle: + # self.db = [float(val.rstrip()) for val in file_handle.readlines()] + # else: + # self.counter = None + # self.db = None + - if self.train: - self.db = [] - else: - # with open(self.ref_file, 'rb') as file_handle: - # self.db = pickle.load(file_handle) - with open(self.ref_file, 'r') as file_handle: - self.db = [float(val.rstrip()) for val in file_handle.readlines()] - else: - self.counter = None - self.db = None # dictionary of real/complex PETSc arch names self.arch = {'real':None,'complex':None} # If we specify the test type, verify that the $PETSC_ARCH contains 'real' or 'complex', @@ -44,15 +48,30 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - self.save() - - def save(self): - if self.rank == 0 and self.train: - # with open(self.ref_file, 'wb') as file_handle: - # pickle.dump(self.db, file_handle) - with open(self.ref_file, 'w') as file_handle: - file_handle.writelines('%23.16e\n' % val for val in self.db) + # self.save() + pass + + def setRef(self, ref): + self.db = ref + + def getRef(self): + return self.db + + # def save(self): + # if self.rank == 0 and self.train: + # # with open(self.ref_file, 'wb') as file_handle: + # # pickle.dump(self.db, file_handle) + + # with open(self.ref_file, 'w') as file_handle: + # file_handle.writelines('%23.16e\n' % val for val in self.db) + # with open(output_file, 'w') as fid: + # ref_str = pprint.pformat(ref) + # fid.write('from numpy import array\n\n') + # fid.write( 'ref = ' + ref_str ) + + + def checkPETScArch(self): # Determine real/complex petsc arches: take the one when the script is # called to be the real: @@ -82,75 +101,89 @@ def root_print(self, s): print(s) # Add values from root only - def root_add_val(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): + def root_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): """Add values but only on the root proc""" if self.rank == 0: - self._add_values(values, rel_tol, abs_tol, msg) + self._add_values(values, name, rel_tol, abs_tol) - def root_add_dict(self, d, rel_tol=1e-12, abs_tol=1e-12, msg=None): + def root_add_dict(self, d, name, rel_tol=1e-12, abs_tol=1e-12): """Only write from the root proc""" if self.rank == 0: - self._add_dict(d, rel_tol, abs_tol, msg) + self._add_dict(d, name, rel_tol, abs_tol) # Add values from all processors - def par_add_val(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): + def par_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): """Add value(values) from parallel process in sorted order""" values = self.comm.gather(values) if self.rank == 0: for i in range(len(values)): print ('Value(s) on processor: %d'%i) - self._add_values(values[i], rel_tol, abs_tol, msg) + self._add_values(values[i], name, rel_tol, abs_tol) - def par_add_sum(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): + def par_add_sum(self, values, name, rel_tol=1e-12, abs_tol=1e-12): """Add the sum of sum of the values from all processors.""" reducedSum = self.comm.reduce(numpy.sum(values)) if self.rank == 0: - self._add_value(reducedSum, rel_tol, abs_tol, msg) + self._add_value(reducedSum, name, rel_tol, abs_tol) - def par_add_norm(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): + def par_add_norm(self, values, name, rel_tol=1e-12, abs_tol=1e-12): """Add the norm across values from all processors.""" reducedSum = self.comm.reduce(numpy.sum(values**2)) if self.rank == 0: - self._add_value(numpy.sqrt(reducedSum), rel_tol, abs_tol, msg) + self._add_value(numpy.sqrt(reducedSum), name, rel_tol, abs_tol) # ***************** # Private functions # ***************** - def _add_value(self, value, rel_tol, abs_tol, msg): + def _add_value(self, value, name, rel_tol, abs_tol, db=None, err_name=None): # We only check floats and integers + if db == None: + db = self.db + + if err_name == None: + err_name = name + + value = numpy.atleast_1d(value).flatten() assert(value.size == 1) + value = value[0] if self.train: - self.db.append(value) + db[name] = value else: - self._check_value(value, self.db[self.counter], rel_tol, abs_tol, msg) + self.assert_allclose(value, db[name], err_name, rel_tol, abs_tol) - self.counter += 1 - def _check_value(self, actual, reference, rel_tol, abs_tol, msg): - if msg is None: - msg = "N/A" - msg = "Failed value for: {}".format(msg) + def assert_allclose(self, actual, reference, name, rel_tol, abs_tol): + msg = "Failed value for: {}".format(name) numpy.testing.assert_allclose(actual, reference, rtol=rel_tol, atol=abs_tol, err_msg=msg) - def _add_values(self, values, rel_tol, abs_tol, msg): + + def _add_values(self, values, *args, **kwargs): '''Add values in special value format''' values = numpy.atleast_1d(values) values = values.flatten() for val in values: - self._add_value(val, rel_tol, abs_tol, msg) + self._add_value(val, *args, **kwargs) - def _add_dict(self, d, rel_tol, abs_tol, msg): + def _add_dict(self, d, dict_name, rel_tol, abs_tol): """Add all values in a dictionary in sorted key order""" + + if self.train: + self.db[dict_name] = {} + for key in sorted(d.keys()): - if msg is None: - key_msg = key - else: - key_msg = msg+': '+key - if isinstance(d[key],dict): - self._add_dict(d[key], rel_tol, abs_tol,key_msg) - elif type(d[key]) == bool: - self._add_value(int(d[key]), rel_tol, abs_tol, key_msg) + + # if msg is None: + # key_msg = key + key_msg = dict_name+': '+key + + + # if isinstance(d[key],dict): + # self._add_dict(d[key], dict_name, rel_tol, abs_tol) + + if type(d[key]) == bool: + self._add_value(int(d[key]), key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) + else: - self._add_values(d[key], rel_tol, abs_tol, key_msg) + self._add_values(d[key], key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) From 5370737d6447693862983d9c34f04610e720974b Mon Sep 17 00:00:00 2001 From: joanibal Date: Sat, 9 May 2020 11:21:36 -0400 Subject: [PATCH 06/21] checkpoint --- python/BaseRegTest.py | 34 ++-------------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/python/BaseRegTest.py b/python/BaseRegTest.py index 8a5a7c0..ca20290 100644 --- a/python/BaseRegTest.py +++ b/python/BaseRegTest.py @@ -11,10 +11,6 @@ def __init__(self, ref , train=False, comm=None, check_arch=False): self.setRef(ref) self.train = train - # if not self.train: - # # We need to check here that the reference file exists, otherwise - # # it will hang when it tries to open it on the root proc. - # assert(os.path.isfile(self.ref_file)) if comm is not None: self.comm = comm @@ -22,20 +18,6 @@ def __init__(self, ref , train=False, comm=None, check_arch=False): self.comm = MPI.COMM_WORLD self.rank = self.comm.rank - # if self.rank == 0: - # self.counter = 0 - - # if self.train: - # self.db = [] - # else: - # # with open(self.ref_file, 'rb') as file_handle: - # # self.db = pickle.load(file_handle) - # with open(self.ref_file, 'r') as file_handle: - # self.db = [float(val.rstrip()) for val in file_handle.readlines()] - # else: - # self.counter = None - # self.db = None - # dictionary of real/complex PETSc arch names self.arch = {'real':None,'complex':None} @@ -57,20 +39,8 @@ def setRef(self, ref): def getRef(self): return self.db - # def save(self): - # if self.rank == 0 and self.train: - # # with open(self.ref_file, 'wb') as file_handle: - # # pickle.dump(self.db, file_handle) - - # with open(self.ref_file, 'w') as file_handle: - # file_handle.writelines('%23.16e\n' % val for val in self.db) - - # with open(output_file, 'w') as fid: - # ref_str = pprint.pformat(ref) - # fid.write('from numpy import array\n\n') - # fid.write( 'ref = ' + ref_str ) - - + def setMode(self,train=False): + self.train = train def checkPETScArch(self): # Determine real/complex petsc arches: take the one when the script is From 0fd529b7fbce2739b8d0205c2e9b56a8e754d5e4 Mon Sep 17 00:00:00 2001 From: joanibal Date: Sat, 9 May 2020 11:22:45 -0400 Subject: [PATCH 07/21] checkpiont --- python/pyAero_problem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index 08f474c..82c96d7 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -654,6 +654,7 @@ def setDesignVars(self, x): design variable names this object needs """ + for dvName in self.DVs: if dvName in x: key = self.DVs[dvName].key From 2afd747bf998e523911d82ac37a5c07e3ce9283b Mon Sep 17 00:00:00 2001 From: joanibal Date: Thu, 21 May 2020 11:42:28 -0400 Subject: [PATCH 08/21] set bcd data by array --- python/pyAero_problem.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/pyAero_problem.py b/python/pyAero_problem.py index 82c96d7..50bae4c 100644 --- a/python/pyAero_problem.py +++ b/python/pyAero_problem.py @@ -464,6 +464,15 @@ def setBCVar(self, varName, value, groupName): def getBCData(self): return self.BCData + def setBCDataArray(self, groupName, varName, dataArray, patch=None): + if patch == None: + # assume the data is set on the first patch in this group + patches = self.BCData[groupName][varName].keys() + self.BCData[groupName][varName][patches[0]] = dataArray + else: + self.BCData[groupName][varName][patch] = dataArray + + def setActuatorVar(self, varName, value, groupName): if not groupName in self.actuatorData.keys(): self.actuatorData[groupName] = {} From 038cb938e1799c11d1decd342398b1021c1252a4 Mon Sep 17 00:00:00 2001 From: joanibal Date: Tue, 2 Jun 2020 13:15:34 -0400 Subject: [PATCH 09/21] added check vals --- python/BaseRegTest.py | 74 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/python/BaseRegTest.py b/python/BaseRegTest.py index 8a5a7c0..09ca98c 100644 --- a/python/BaseRegTest.py +++ b/python/BaseRegTest.py @@ -159,31 +159,79 @@ def assert_allclose(self, actual, reference, name, rel_tol, abs_tol): numpy.testing.assert_allclose(actual, reference, rtol=rel_tol, atol=abs_tol, err_msg=msg) - def _add_values(self, values, *args, **kwargs): + def _add_values(self, values, name, rel_tol, abs_tol, db=None, err_name=None): '''Add values in special value format''' - values = numpy.atleast_1d(values) - values = values.flatten() - for val in values: - self._add_value(val, *args, **kwargs) + # values = numpy.atleast_1d(values) + # values = values.flatten() + # for val in values: + # self._add_value(val, *args, **kwargs) - def _add_dict(self, d, dict_name, rel_tol, abs_tol): + if db == None: + db = self.db + + if err_name == None: + err_name = name + + if self.train: + db[name] = values + else: + self.assert_allclose(values, db[name], err_name, rel_tol, abs_tol) + + + def _add_dict(self, d, dict_name, rel_tol, abs_tol, db=None, err_name=None): """Add all values in a dictionary in sorted key order""" if self.train: self.db[dict_name] = {} + if db == None: + db = self.db + + + for key in sorted(d.keys()): - + print(dict_name, key) # if msg is None: # key_msg = key - key_msg = dict_name+': '+key - + if err_name: + key_msg = err_name+ ':' + dict_name+': '+key + else: + key_msg = dict_name+': '+key # if isinstance(d[key],dict): # self._add_dict(d[key], dict_name, rel_tol, abs_tol) - if type(d[key]) == bool: - self._add_value(int(d[key]), key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) - + self._add_value(int(d[key]), key, rel_tol, abs_tol, db=db[dict_name], err_name=key_msg) + if isinstance(d[key], dict): + # do some good ol' fashion recursion + self._add_dict(d[key], key, rel_tol, abs_tol, db=db[dict_name], err_name=dict_name) else: - self._add_values(d[key], key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) + self._add_values(d[key], key, rel_tol, abs_tol, db=db[dict_name], err_name=key_msg) + + + # ***************** + # Static helper method + # ***************** + + @staticmethod + def setLocalPaths(baseDir, sys_path): + """added the necessary paths to the version of the files within the same + repo""" + repoDir = baseDir.split('/tests/')[0] + sys_path.append(repoDir) + + testDir = baseDir.split('/reg_tests/')[0] + regTestDir = testDir + '/reg_tests' + sys_path.append(regTestDir) + + @staticmethod + def getLocalDirPaths(baseDir): + """Returns the paths to the reference files and input and outputs based on the + directory of the file (baseDir)""" + refDir = baseDir.replace('reg_tests','reg_tests/refs') + + testDir = baseDir.split('/reg_tests')[0] + inputDir = os.path.join(testDir,'input_files') + outputDir = os.path.join(testDir,'output_files') + + return refDir, inputDir, outputDir From d1e14f6f4797d7a586b2c0a44fe621f5877ff57d Mon Sep 17 00:00:00 2001 From: joanibal Date: Thu, 11 Jun 2020 16:29:26 -0400 Subject: [PATCH 10/21] using setup to install --- __init__.py | 25 -------------- {python => baseclasses}/BaseRegTest.py | 0 {python => baseclasses}/BaseSolver.py | 0 {python => baseclasses}/FluidProperties.py | 0 {python => baseclasses}/ICAOAtmosphere.py | 0 baseclasses/__init__.py | 28 +++++++++++++++ {python => baseclasses}/py3Util.py | 0 .../pyAeroStruct_problem.py | 0 {python => baseclasses}/pyAero_geometry.py | 0 {python => baseclasses}/pyAero_problem.py | 0 {python => baseclasses}/pyAero_solver.py | 0 {python => baseclasses}/pyEngine_problem.py | 0 .../pyFieldPerformance_problem.py | 0 {python => baseclasses}/pyLG_problem.py | 0 {python => baseclasses}/pyMission_problem.py | 0 {python => baseclasses}/pyStruct_problem.py | 0 {python => baseclasses}/pyTransi_problem.py | 0 {python => baseclasses}/pyWeight_problem.py | 0 python/__init__.py | 1 - setup.py | 34 +++++++++++++++++++ 20 files changed, 62 insertions(+), 26 deletions(-) delete mode 100644 __init__.py rename {python => baseclasses}/BaseRegTest.py (100%) rename {python => baseclasses}/BaseSolver.py (100%) rename {python => baseclasses}/FluidProperties.py (100%) rename {python => baseclasses}/ICAOAtmosphere.py (100%) create mode 100644 baseclasses/__init__.py rename {python => baseclasses}/py3Util.py (100%) rename {python => baseclasses}/pyAeroStruct_problem.py (100%) rename {python => baseclasses}/pyAero_geometry.py (100%) rename {python => baseclasses}/pyAero_problem.py (100%) rename {python => baseclasses}/pyAero_solver.py (100%) rename {python => baseclasses}/pyEngine_problem.py (100%) rename {python => baseclasses}/pyFieldPerformance_problem.py (100%) rename {python => baseclasses}/pyLG_problem.py (100%) rename {python => baseclasses}/pyMission_problem.py (100%) rename {python => baseclasses}/pyStruct_problem.py (100%) rename {python => baseclasses}/pyTransi_problem.py (100%) rename {python => baseclasses}/pyWeight_problem.py (100%) delete mode 100644 python/__init__.py create mode 100644 setup.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 4a242ec..0000000 --- a/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from .python.pyAero_problem import AeroProblem -from .python.pyTransi_problem import TransiProblem -from .python.pyStruct_problem import StructProblem -from .python.pyAeroStruct_problem import AeroStructProblem - -from .python.pyAero_solver import AeroSolver -from .python.BaseSolver import BaseSolver - -from .python.pyMission_problem import MissionProblem -from .python.pyMission_problem import MissionProfile -from .python.pyMission_problem import MissionSegment - -from .python.pyWeight_problem import WeightProblem -from .python.pyWeight_problem import FuelCase - -from .python.FluidProperties import FluidProperties -from .python.ICAOAtmosphere import ICAOAtmosphere -from .python.pyEngine_problem import EngineProblem - -from .python.pyFieldPerformance_problem import FieldPerformanceProblem - -from .python.pyLG_problem import LGProblem - -from .python.py3Util import getPy3SafeString -from .python.BaseRegTest import BaseRegTest \ No newline at end of file diff --git a/python/BaseRegTest.py b/baseclasses/BaseRegTest.py similarity index 100% rename from python/BaseRegTest.py rename to baseclasses/BaseRegTest.py diff --git a/python/BaseSolver.py b/baseclasses/BaseSolver.py similarity index 100% rename from python/BaseSolver.py rename to baseclasses/BaseSolver.py diff --git a/python/FluidProperties.py b/baseclasses/FluidProperties.py similarity index 100% rename from python/FluidProperties.py rename to baseclasses/FluidProperties.py diff --git a/python/ICAOAtmosphere.py b/baseclasses/ICAOAtmosphere.py similarity index 100% rename from python/ICAOAtmosphere.py rename to baseclasses/ICAOAtmosphere.py diff --git a/baseclasses/__init__.py b/baseclasses/__init__.py new file mode 100644 index 0000000..4b4eb2f --- /dev/null +++ b/baseclasses/__init__.py @@ -0,0 +1,28 @@ +# This page is intentially left blank +__version__ = '1.1.0' + +from .pyAero_problem import AeroProblem +from .pyTransi_problem import TransiProblem +from .pyStruct_problem import StructProblem +from .pyAeroStruct_problem import AeroStructProblem + +from .pyAero_solver import AeroSolver +from .BaseSolver import BaseSolver + +from .pyMission_problem import MissionProblem +from .pyMission_problem import MissionProfile +from .pyMission_problem import MissionSegment + +from .pyWeight_problem import WeightProblem +from .pyWeight_problem import FuelCase + +from .FluidProperties import FluidProperties +from .ICAOAtmosphere import ICAOAtmosphere +from .pyEngine_problem import EngineProblem + +from .pyFieldPerformance_problem import FieldPerformanceProblem + +from .pyLG_problem import LGProblem + +from .py3Util import getPy3SafeString +from .BaseRegTest import BaseRegTest \ No newline at end of file diff --git a/python/py3Util.py b/baseclasses/py3Util.py similarity index 100% rename from python/py3Util.py rename to baseclasses/py3Util.py diff --git a/python/pyAeroStruct_problem.py b/baseclasses/pyAeroStruct_problem.py similarity index 100% rename from python/pyAeroStruct_problem.py rename to baseclasses/pyAeroStruct_problem.py diff --git a/python/pyAero_geometry.py b/baseclasses/pyAero_geometry.py similarity index 100% rename from python/pyAero_geometry.py rename to baseclasses/pyAero_geometry.py diff --git a/python/pyAero_problem.py b/baseclasses/pyAero_problem.py similarity index 100% rename from python/pyAero_problem.py rename to baseclasses/pyAero_problem.py diff --git a/python/pyAero_solver.py b/baseclasses/pyAero_solver.py similarity index 100% rename from python/pyAero_solver.py rename to baseclasses/pyAero_solver.py diff --git a/python/pyEngine_problem.py b/baseclasses/pyEngine_problem.py similarity index 100% rename from python/pyEngine_problem.py rename to baseclasses/pyEngine_problem.py diff --git a/python/pyFieldPerformance_problem.py b/baseclasses/pyFieldPerformance_problem.py similarity index 100% rename from python/pyFieldPerformance_problem.py rename to baseclasses/pyFieldPerformance_problem.py diff --git a/python/pyLG_problem.py b/baseclasses/pyLG_problem.py similarity index 100% rename from python/pyLG_problem.py rename to baseclasses/pyLG_problem.py diff --git a/python/pyMission_problem.py b/baseclasses/pyMission_problem.py similarity index 100% rename from python/pyMission_problem.py rename to baseclasses/pyMission_problem.py diff --git a/python/pyStruct_problem.py b/baseclasses/pyStruct_problem.py similarity index 100% rename from python/pyStruct_problem.py rename to baseclasses/pyStruct_problem.py diff --git a/python/pyTransi_problem.py b/baseclasses/pyTransi_problem.py similarity index 100% rename from python/pyTransi_problem.py rename to baseclasses/pyTransi_problem.py diff --git a/python/pyWeight_problem.py b/baseclasses/pyWeight_problem.py similarity index 100% rename from python/pyWeight_problem.py rename to baseclasses/pyWeight_problem.py diff --git a/python/__init__.py b/python/__init__.py deleted file mode 100644 index 145e2e8..0000000 --- a/python/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This page is intentially left blank diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e1c7102 --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +from setuptools import setup +import re + +__version__ = re.findall( + r"""__version__ = ["']+([0-9\.]*)["']+""", + open('baseclasses/__init__.py').read(), +)[0] + +setup(name='baseclasses', + version=__version__, + description="baseclasses contains base classes that are used together with the rest of MDO lab tools.", + long_description=""" + baseclasses contains, well, base classes that are used together with the rest of MDO lab tools. It includes the various problems to be defined by the user in order to perform some analyses, such as + + AeroProblem + StructProblem + AeroStructProblem + + It also contains some class definitions shared by various solvers, such as AeroSolver. Finally, it also contains a class, BaseRegTest, which is used as part of the testing toolchain. + """, + long_description_content_type="text/markdown", + keywords='optimization shape-optimization multi-disciplinary', + author='', + author_email='', + url='https://github.com/mdolab/baseclasses', + license='Apache License Version 2.0', + packages=[ + 'baseclasses', + ], + classifiers=[ + "Operating System :: OS Independent", + "Programming Language :: Python"] + ) + From 55ec54c0368488d67bb7a87ac7323aed24070876 Mon Sep 17 00:00:00 2001 From: joanibal Date: Sat, 13 Jun 2020 14:13:29 -0400 Subject: [PATCH 11/21] BaseRegTest fixed --- baseclasses/BaseRegTest.py | 199 +++++++++++-------------------------- 1 file changed, 59 insertions(+), 140 deletions(-) diff --git a/baseclasses/BaseRegTest.py b/baseclasses/BaseRegTest.py index 09ca98c..c186107 100644 --- a/baseclasses/BaseRegTest.py +++ b/baseclasses/BaseRegTest.py @@ -1,20 +1,18 @@ from __future__ import print_function from mpi4py import MPI +import pickle import numpy import os -import pprint class BaseRegTest(object): - def __init__(self, ref , train=False, comm=None, check_arch=False): - # self.ref_file = ref_file - - self.setRef(ref) + def __init__(self, ref_file, train=False, comm=None, check_arch=False): + self.ref_file = ref_file self.train = train - # if not self.train: - # # We need to check here that the reference file exists, otherwise - # # it will hang when it tries to open it on the root proc. - # assert(os.path.isfile(self.ref_file)) + if not self.train: + # We need to check here that the reference file exists, otherwise + # it will hang when it tries to open it on the root proc. + assert(os.path.isfile(self.ref_file)) if comm is not None: self.comm = comm @@ -22,21 +20,19 @@ def __init__(self, ref , train=False, comm=None, check_arch=False): self.comm = MPI.COMM_WORLD self.rank = self.comm.rank - # if self.rank == 0: - # self.counter = 0 - - # if self.train: - # self.db = [] - # else: - # # with open(self.ref_file, 'rb') as file_handle: - # # self.db = pickle.load(file_handle) - # with open(self.ref_file, 'r') as file_handle: - # self.db = [float(val.rstrip()) for val in file_handle.readlines()] - # else: - # self.counter = None - # self.db = None - + if self.rank == 0: + self.counter = 0 + if self.train: + self.db = [] + else: + # with open(self.ref_file, 'rb') as file_handle: + # self.db = pickle.load(file_handle) + with open(self.ref_file, 'r') as file_handle: + self.db = [float(val.rstrip()) for val in file_handle.readlines()] + else: + self.counter = None + self.db = None # dictionary of real/complex PETSc arch names self.arch = {'real':None,'complex':None} # If we specify the test type, verify that the $PETSC_ARCH contains 'real' or 'complex', @@ -48,30 +44,15 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - # self.save() - pass - - def setRef(self, ref): - self.db = ref - - def getRef(self): - return self.db - - # def save(self): - # if self.rank == 0 and self.train: - # # with open(self.ref_file, 'wb') as file_handle: - # # pickle.dump(self.db, file_handle) - - # with open(self.ref_file, 'w') as file_handle: - # file_handle.writelines('%23.16e\n' % val for val in self.db) + self.save() + + def save(self): + if self.rank == 0 and self.train: + # with open(self.ref_file, 'wb') as file_handle: + # pickle.dump(self.db, file_handle) + with open(self.ref_file, 'w') as file_handle: + file_handle.writelines('%23.16e\n' % val for val in self.db) - # with open(output_file, 'w') as fid: - # ref_str = pprint.pformat(ref) - # fid.write('from numpy import array\n\n') - # fid.write( 'ref = ' + ref_str ) - - - def checkPETScArch(self): # Determine real/complex petsc arches: take the one when the script is # called to be the real: @@ -101,137 +82,75 @@ def root_print(self, s): print(s) # Add values from root only - def root_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): + def root_add_val(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): """Add values but only on the root proc""" if self.rank == 0: - self._add_values(values, name, rel_tol, abs_tol) + self._add_values(values, rel_tol, abs_tol, msg) - def root_add_dict(self, d, name, rel_tol=1e-12, abs_tol=1e-12): + def root_add_dict(self, d, rel_tol=1e-12, abs_tol=1e-12, msg=None): """Only write from the root proc""" if self.rank == 0: - self._add_dict(d, name, rel_tol, abs_tol) + self._add_dict(d, rel_tol, abs_tol, msg) # Add values from all processors - def par_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): + def par_add_val(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): """Add value(values) from parallel process in sorted order""" values = self.comm.gather(values) if self.rank == 0: for i in range(len(values)): print ('Value(s) on processor: %d'%i) - self._add_values(values[i], name, rel_tol, abs_tol) + self._add_values(values[i], rel_tol, abs_tol, msg) - def par_add_sum(self, values, name, rel_tol=1e-12, abs_tol=1e-12): + def par_add_sum(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): """Add the sum of sum of the values from all processors.""" reducedSum = self.comm.reduce(numpy.sum(values)) if self.rank == 0: - self._add_value(reducedSum, name, rel_tol, abs_tol) + self._add_value(reducedSum, rel_tol, abs_tol, msg) - def par_add_norm(self, values, name, rel_tol=1e-12, abs_tol=1e-12): + def par_add_norm(self, values, rel_tol=1e-12, abs_tol=1e-12, msg=None): """Add the norm across values from all processors.""" reducedSum = self.comm.reduce(numpy.sum(values**2)) if self.rank == 0: - self._add_value(numpy.sqrt(reducedSum), name, rel_tol, abs_tol) + self._add_value(numpy.sqrt(reducedSum), rel_tol, abs_tol, msg) # ***************** # Private functions # ***************** - def _add_value(self, value, name, rel_tol, abs_tol, db=None, err_name=None): + def _add_value(self, value, rel_tol, abs_tol, msg): # We only check floats and integers - if db == None: - db = self.db - - if err_name == None: - err_name = name - - value = numpy.atleast_1d(value).flatten() assert(value.size == 1) - value = value[0] if self.train: - db[name] = value + self.db.append(value) else: - self.assert_allclose(value, db[name], err_name, rel_tol, abs_tol) + self._check_value(value, self.db[self.counter], rel_tol, abs_tol, msg) + self.counter += 1 - def assert_allclose(self, actual, reference, name, rel_tol, abs_tol): - msg = "Failed value for: {}".format(name) + def _check_value(self, actual, reference, rel_tol, abs_tol, msg): + if msg is None: + msg = "N/A" + msg = "Failed value for: {}".format(msg) numpy.testing.assert_allclose(actual, reference, rtol=rel_tol, atol=abs_tol, err_msg=msg) - - def _add_values(self, values, name, rel_tol, abs_tol, db=None, err_name=None): + def _add_values(self, values, rel_tol, abs_tol, msg): '''Add values in special value format''' - # values = numpy.atleast_1d(values) - # values = values.flatten() - # for val in values: - # self._add_value(val, *args, **kwargs) - - if db == None: - db = self.db - - if err_name == None: - err_name = name + values = numpy.atleast_1d(values) + values = values.flatten() + for val in values: + self._add_value(val, rel_tol, abs_tol, msg) - if self.train: - db[name] = values - else: - self.assert_allclose(values, db[name], err_name, rel_tol, abs_tol) - - - def _add_dict(self, d, dict_name, rel_tol, abs_tol, db=None, err_name=None): + def _add_dict(self, d, rel_tol, abs_tol, msg): """Add all values in a dictionary in sorted key order""" - - if self.train: - self.db[dict_name] = {} - - if db == None: - db = self.db - - - for key in sorted(d.keys()): - print(dict_name, key) - # if msg is None: - # key_msg = key - if err_name: - key_msg = err_name+ ':' + dict_name+': '+key + if msg is None: + key_msg = key else: - key_msg = dict_name+': '+key - - # if isinstance(d[key],dict): - # self._add_dict(d[key], dict_name, rel_tol, abs_tol) - if type(d[key]) == bool: - self._add_value(int(d[key]), key, rel_tol, abs_tol, db=db[dict_name], err_name=key_msg) - if isinstance(d[key], dict): - # do some good ol' fashion recursion - self._add_dict(d[key], key, rel_tol, abs_tol, db=db[dict_name], err_name=dict_name) + key_msg = msg+': '+key + if isinstance(d[key],dict): + self._add_dict(d[key], rel_tol, abs_tol,key_msg) + elif type(d[key]) == bool: + self._add_value(int(d[key]), rel_tol, abs_tol, key_msg) else: - self._add_values(d[key], key, rel_tol, abs_tol, db=db[dict_name], err_name=key_msg) - - - # ***************** - # Static helper method - # ***************** - - @staticmethod - def setLocalPaths(baseDir, sys_path): - """added the necessary paths to the version of the files within the same - repo""" - repoDir = baseDir.split('/tests/')[0] - sys_path.append(repoDir) - - testDir = baseDir.split('/reg_tests/')[0] - regTestDir = testDir + '/reg_tests' - sys_path.append(regTestDir) - - @staticmethod - def getLocalDirPaths(baseDir): - """Returns the paths to the reference files and input and outputs based on the - directory of the file (baseDir)""" - refDir = baseDir.replace('reg_tests','reg_tests/refs') - - testDir = baseDir.split('/reg_tests')[0] - inputDir = os.path.join(testDir,'input_files') - outputDir = os.path.join(testDir,'output_files') - - return refDir, inputDir, outputDir + self._add_values(d[key], rel_tol, abs_tol, key_msg) From 231e37730a74e18b4de737f433c2a58ae5da4e3a Mon Sep 17 00:00:00 2001 From: joanibal Date: Mon, 15 Jun 2020 18:02:07 -0400 Subject: [PATCH 12/21] bump version numbers --- baseclasses/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baseclasses/__init__.py b/baseclasses/__init__.py index 4b4eb2f..58034ad 100644 --- a/baseclasses/__init__.py +++ b/baseclasses/__init__.py @@ -1,5 +1,5 @@ # This page is intentially left blank -__version__ = '1.1.0' +__version__ = '1.2.0' from .pyAero_problem import AeroProblem from .pyTransi_problem import TransiProblem @@ -25,4 +25,4 @@ from .pyLG_problem import LGProblem from .py3Util import getPy3SafeString -from .BaseRegTest import BaseRegTest \ No newline at end of file +from .BaseRegTest import BaseRegTest From a33c2ec7a6ed5eff4a1ab8ae6db3d323aaca23fd Mon Sep 17 00:00:00 2001 From: joanibal Date: Mon, 24 Aug 2020 15:11:04 -0400 Subject: [PATCH 13/21] bcdata update --- baseclasses/pyAero_problem.py | 319 +++++++++++++++++++++++----------- 1 file changed, 215 insertions(+), 104 deletions(-) diff --git a/baseclasses/pyAero_problem.py b/baseclasses/pyAero_problem.py index b350fe0..44567f3 100644 --- a/baseclasses/pyAero_problem.py +++ b/baseclasses/pyAero_problem.py @@ -17,7 +17,9 @@ import warnings from .ICAOAtmosphere import ICAOAtmosphere from .FluidProperties import FluidProperties +import copy +from collections import defaultdict class CaseInsensitiveDict(dict): def __setitem__(self, key, value): @@ -346,7 +348,7 @@ def __init__(self, name, **kwargs): # Storage of BC varible values # vars are keyed by (bcVarName, Family) - self.BCData = {} + self.bc_data = {} self.actuatorData = {} def _setStates(self, inputDict): @@ -455,21 +457,21 @@ def setBCVar(self, varName, value, groupName): """ set the value of a BC variable on a specific variable """ - if not groupName in self.BCData.keys(): - self.BCData[groupName] = {} + if not groupName in self.bc_data.keys(): + self.bc_data[groupName] = {} - self.BCData[groupName][varName] = value + self.bc_data[groupName][varName] = value def getBCData(self): - return self.BCData + return self.bc_data - def setBCDataArray(self, groupName, varName, dataArray, patch=None): - if patch == None: - # assume the data is set on the first patch in this group - patches = self.BCData[groupName][varName].keys() - self.BCData[groupName][varName][patches[0]] = dataArray - else: - self.BCData[groupName][varName][patch] = dataArray + # def setBCArray(self, groupName, varName, dataArray, patch=None): + # if patch == None: + # # assume the data is set on the first patch in this group + # patches = self.BCData[groupName][varName].keys() + # self.BCData[groupName][varName][patches[0]] = dataArray + # else: + # self.BCData[groupName][varName][patch] = dataArray def setActuatorVar(self, varName, value, groupName): @@ -481,6 +483,44 @@ def setActuatorVar(self, varName, value, groupName): def getActuatorData(self): return self.actuatorData + # def addBCDV(self, bc_var, value=None, lower=None, upper=None, scale=1.0, + # name=None, offset=0.0, dvOffset=0.0, addToPyOpt=True, familyGroup=None, + # units=None, **kwargs): + # """ + # add a set of bcdata was a design variable + # """ + + + # if bc_var not in self.possibleBCDVs: + # raise ValueError('%s is not a valid design variable' % key) + + + # # get a numpy array by appliying thee filters to the bc data + # data = self.bc_data.getBCArraysFlatData( bc_var, familyGroup=familyGroup) + + # if name is None: + # dvName = '%s_%s_%s' % (key, familyGroup, self.name) + # else: + # dvName = name + + + # # combine the data + + # if value is None: + # value = data + # else: + # # insure that the given size is correct if it is not a float + # # + # if (not isinstance(value, float)) and value.size == data.size: + # raise Error('give value for {} is not ht right size, {} given {} expected'.format(bc_var, value.size, data.size)) + + # dv = aeroDV(bc_var, value, lower, upper, scale, offset, + # dvOffset, addToPyOpt, familyGroup, units) + # self.DVs[dvName] = dv + # return + + + def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, name=None, offset=0.0, dvOffset=0.0, addToPyOpt=True, familyGroup=None, units=None): @@ -574,10 +614,11 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, else: dvName = name + if value is None: if key in self.possibleBCDVs: try: - value = self.BCData[familyGroup][key] + value = self.bc_data[familyGroup][key] except KeyError : raise Error("The value must be given or set using the setBCVar routine") else: @@ -586,46 +627,46 @@ def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, except KeyError : raise Error("The value must be given or set using the setActuatorVar routine") - # the value of the BCData[familyGroup][key] maybe a dictionary of data for each patch - # to accomadate this we will add a variable for each entry in the dictionary - if isinstance(value, dict) and key in self.possibleBCDVs: + # # the value of the BCData[familyGroup][key] maybe a dictionary of data for each patch + # # to accomadate this we will add a variable for each entry in the dictionary + # if isinstance(value, dict) and key in self.possibleBCDVs: - # use a little bit o recursion - for dict_key in value: - dict_key_name = str(dict_key).replace(" ", "") - dict_dvName = '%s_%s' % (dvName , dict_key_name) - - if isinstance(lower, dict): - lower_val = lower[dict_key] - else: - lower_val = lower - if isinstance(upper, dict): - upper_val = upper[dict_key] - else: - upper_val = upper - if isinstance(scale, dict): - scale_val = scale[dict_key] - else: - scale_val = scale - if isinstance(offset, dict): - offset_val = offset[dict_key] - else: - offset_val = offset - if isinstance(dvOffset, dict): - dvOffset_val = dvOffset[dict_key] - else: - dvOffset_val = dvOffset - - - # self.addDV(key, , lower_val, upper_val, scale_val, dvName, - # offset_val, dvOffset_val, addToPyOpt, familyGroup, - # units) - - self.DVs[dict_dvName] = aeroDV(key, value[dict_key], lower_val, upper_val, scale_val, offset_val, - dvOffset_val, addToPyOpt, familyGroup, units) - self.DVs[dict_dvName].dict_key = dict_key + # # use a little bit o recursion + # for dict_key in value: + # dict_key_name = str(dict_key).replace(" ", "") + # dict_dvName = '%s_%s' % (dvName , dict_key_name) + + # if isinstance(lower, dict): + # lower_val = lower[dict_key] + # else: + # lower_val = lower + # if isinstance(upper, dict): + # upper_val = upper[dict_key] + # else: + # upper_val = upper + # if isinstance(scale, dict): + # scale_val = scale[dict_key] + # else: + # scale_val = scale + # if isinstance(offset, dict): + # offset_val = offset[dict_key] + # else: + # offset_val = offset + # if isinstance(dvOffset, dict): + # dvOffset_val = dvOffset[dict_key] + # else: + # dvOffset_val = dvOffset + + + # # self.addDV(key, , lower_val, upper_val, scale_val, dvName, + # # offset_val, dvOffset_val, addToPyOpt, familyGroup, + # # units) + + # self.DVs[dict_dvName] = aeroDV(key, value[dict_key], lower_val, upper_val, scale_val, offset_val, + # dvOffset_val, addToPyOpt, familyGroup, units) + # self.DVs[dict_dvName].dict_key = dict_key - return + # return else: if name is None: @@ -665,22 +706,19 @@ def setDesignVars(self, x): for dvName in self.DVs: if dvName in x: - key = self.DVs[dvName].key - family = self.DVs[dvName].family - value = x[dvName] + self.DVs[dvName].offset + dv = self.DVs[dvName] + key = dv.key + family = dv.family + value = x[dvName] + dv.offset if key in self.possibleBCDVs: + self.bc_data[family][key] = value - if hasattr(self.DVs[dvName], 'dict_key'): - dict_key = self.DVs[dvName].dict_key - self.BCData[family][key][dict_key] = value - else: - self.BCData[family][key] = value elif key in self.possibleActuatorDVs: self.actuatorData[family][key] = value else: setattr(self, key, value) - self.DVs[dvName].value = x[dvName] + dv.value = x[dvName] # try: # To set in the DV as well if the DV exists: # except: # # DV doesn't exist @@ -873,50 +911,6 @@ def altitude(self, value): self._setStates({'altitude': value}) self._set_aeroDV_val('altitude', value) - # def _update(self): - # """ - # Try to finish the complete state: - # """ - - # if self.T is not None: - # self.a = numpy.sqrt(self.gamma*self.R*self.T) - # if self.englishUnits: - # mu = (self.muSuthDim * ( - # (self.TSuthDim + self.SSuthDim) / (self.T/1.8 + self.SSuthDim)) * - # (((self.T/1.8)/self.TSuthDim)**1.5)) - # self.mu = mu / 47.9 - # else: - # self.mu = (self.muSuthDim * ( - # (self.TSuthDim + self.SSuthDim) / (self.T + self.SSuthDim)) * - # ((self.T/self.TSuthDim)**1.5)) - - # if self.mach is not None and self.a is not None: - # self.V = self.mach * self.a - - # if self.a is not None and self.V is not None: - # self.__dict__['mach'] = self.V/self.a - - # if self.P is not None and self.T is not None: - # self.__dict__['rho'] = self.P/(self.R*self.T) - - # if self.rho is not None and self.T is not None: - # self.__dict__['P'] = self.rho*self.R*self.T - - # if self.rho is not None and self.P is not None: - # self.__dict__['T'] = self.P /(self.rho*self.R) - - # if self.mu is not None and self.rho is not None: - # self.nu = self.mu / self.rho - - # if self.rho is not None and self.V is not None: - # self.q = 0.5*self.rho*self.V**2 - - # if self.rho is not None and self.V is not None and self.mu is not None: - # self.__dict__['re'] = self.rho*self.V/self.mu - - # if self.re is not None and self.mu is not None and self.V is not None: - # self.__dict__['rho'] = self.re*self.mu/self.V - def _updateFromRe(self): ''' update the full set of states from M,T,P @@ -1006,6 +1000,107 @@ def _getDVSens(self, func): return rDict + def evalDVsSensFwd(self, xDvDot): + """ + fwd mod deriv of setDesignVars + """ + aeroDvsDot = {} + bcDvsDot = defaultdict(lambda: {}) + actDvsDot = defaultdict(lambda: {}) + + for dvName in self.DVs: + if dvName in xDvDot: + dv = self.DVs[dvName] + key = dv.key + family = dv.family + value = xDvDot[dvName] + + if key in self.possibleBCDVs: + bcDvsDot[family][key] = value + + elif key in self.possibleActuatorDVs: + actDvsDot[family][key] = value + else: + aeroDvsDot[key] = value + + return aeroDvsDot, bcDvsDot, actDvsDot + + + def evalDVsSensBwd(self, aeroDvBar, BCDataBar, actDataBar): + """This internal furncion is used to convert the raw array output from + the matrix-free product bwd routine into the required + dictionary format.""" + + DVbar = {} + for dvName in self.DVs: + key = self.DVs[dvName].key.lower() + + tmp = {} + if key == 'altitude': + # This design variable is special. It combines changes + # in temperature, pressure and density into a single + # variable. Since we have derivatives for T, P and + # rho, we simply chain rule it back to the the + # altitude variable. + self.evalFunctionsSens(tmp, ['P', 'T', 'rho']) + + # Extract the derivatives wrt the independent + # parameters in ADflow + dIdP = aeroDvBar['p'] + dIdT = aeroDvBar['t'] + dIdrho = aeroDvBar['rho'] + + # Chain-rule to get the final derivative: + DVbar[dvName] = ( + tmp[self['P']][dvName]*dIdP + + tmp[self['T']][dvName]*dIdT + + tmp[self['rho']][dvName]*dIdrho) + + elif key == 'mach': + self.evalFunctionsSens(tmp, ['P', 'rho']) + # Simular story for Mach: It is technically possible + # to use a mach number for a fixed RE simulation. For + # the RE to stay fixed and change the mach number, the + # 'P' and 'rho' must also change. We have to chain run + # this dependence back through to the final mach + # derivative. When Mach number is used with altitude + # or P and T, this calc is unnecessary, but won't do + # any harm. + dIdP = aeroDvBar['p'] + dIdrho = aeroDvBar['rho'] + + # Chain-rule to get the final derivative: + DVbar[dvName] = ( + tmp[self['P']][dvName]*dIdP + + tmp[self['rho']][dvName]*dIdrho + + aeroDvBar['mach']) + # if the variable is an BC DV, get the data from the BCDataBar + # dict for each patch + elif key in [k.lower() for k in self.possibleBCDVs]: + fam = self.DVs[dvName].family + dv_key = self.DVs[dvName].key + + DVbar[dvName] = BCDataBar[fam][dv_key] + + # if the variable is an actuator DV, get the data from the actDataBar + elif key in [k.lower() for k in self.possibleActuatorDVs]: + fam = self.DVs[dvName].family + dv_key = self.DVs[dvName].key + DVbar[dvName] = actDataBar[fam][dv_key] + + + else: + DVbar[dvName] = aeroDvBar[key] + + + + + return DVbar + + + + # return DVbar + class aeroDV(object): """ @@ -1024,3 +1119,19 @@ def __init__(self, key, value, lower, upper, scale, offset, dvOffset, self.addToPyOpt = addToPyOpt self.family = family self.units = units + + + if isinstance(value, float): + self.size = 1 + elif isinstance(value, numpy.ndarray): + self.size = value.size + else: + size = None + #TODO raise an error? + + def __repr__(self): + '''Returns representation of the object''' + msg = "{}({!r}) {}".format(self.__class__.__name__, self.key, self.value) + if self.units is not None: + msg += self.units + return msg \ No newline at end of file From def6e862eea847f89988727a37371940e8b14d5b Mon Sep 17 00:00:00 2001 From: joanibal Date: Mon, 18 Jan 2021 16:55:56 -0500 Subject: [PATCH 14/21] merge prt2 --- baseclasses/BaseRegTest.py | 40 ++++--- baseclasses/BaseSolver.py | 114 +++++--------------- baseclasses/ICAOAtmosphere.py | 126 ++++++++++------------ baseclasses/pyAero_geometry.py | 184 +++++++++++++------------------- baseclasses/pyLG_problem.py | 148 +++++++++---------------- baseclasses/pyTransi_problem.py | 121 +++++++-------------- 6 files changed, 270 insertions(+), 463 deletions(-) diff --git a/baseclasses/BaseRegTest.py b/baseclasses/BaseRegTest.py index ca20290..1bd34f7 100644 --- a/baseclasses/BaseRegTest.py +++ b/baseclasses/BaseRegTest.py @@ -4,9 +4,9 @@ import os import pprint -class BaseRegTest(object): - def __init__(self, ref , train=False, comm=None, check_arch=False): +class BaseRegTest(object): + def __init__(self, ref, train=False, comm=None, check_arch=False): # self.ref_file = ref_file self.setRef(ref) @@ -20,7 +20,7 @@ def __init__(self, ref , train=False, comm=None, check_arch=False): self.rank = self.comm.rank # dictionary of real/complex PETSc arch names - self.arch = {'real':None,'complex':None} + self.arch = {"real": None, "complex": None} # If we specify the test type, verify that the $PETSC_ARCH contains 'real' or 'complex', # and sets the self.arch flag appropriately if check_arch: @@ -39,28 +39,28 @@ def setRef(self, ref): def getRef(self): return self.db - def setMode(self,train=False): + def setMode(self, train=False): self.train = train def checkPETScArch(self): # Determine real/complex petsc arches: take the one when the script is # called to be the real: - self.arch['real'] = os.environ.get("PETSC_ARCH") + self.arch["real"] = os.environ.get("PETSC_ARCH") pdir = os.environ.get("PETSC_DIR") # Directories in the root of the petsc directory - dirs = [o for o in os.listdir(pdir) if os.path.isdir(pdir+'/'+o)] + dirs = [o for o in os.listdir(pdir) if os.path.isdir(pdir + "/" + o)] # See if any one has 'complex' in it...basically we want to see if # an architecture has 'complex' in it which means it is (most # likely) a complex build carch = [] for d in dirs: - if 'complex' in d.lower(): + if "complex" in d.lower(): carch.append(d) if len(carch) > 0: # take the first one if there are multiple - self.arch['complex'] = carch[0] + self.arch["complex"] = carch[0] else: - self.arch['complex'] = None + self.arch["complex"] = None # ***************** # Public functions @@ -87,7 +87,7 @@ def par_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): values = self.comm.gather(values) if self.rank == 0: for i in range(len(values)): - print ('Value(s) on processor: %d'%i) + print("Value(s) on processor: %d" % i) self._add_values(values[i], name, rel_tol, abs_tol) def par_add_sum(self, values, name, rel_tol=1e-12, abs_tol=1e-12): @@ -98,7 +98,7 @@ def par_add_sum(self, values, name, rel_tol=1e-12, abs_tol=1e-12): def par_add_norm(self, values, name, rel_tol=1e-12, abs_tol=1e-12): """Add the norm across values from all processors.""" - reducedSum = self.comm.reduce(numpy.sum(values**2)) + reducedSum = self.comm.reduce(numpy.sum(values ** 2)) if self.rank == 0: self._add_value(numpy.sqrt(reducedSum), name, rel_tol, abs_tol) @@ -113,9 +113,8 @@ def _add_value(self, value, name, rel_tol, abs_tol, db=None, err_name=None): if err_name == None: err_name = name - value = numpy.atleast_1d(value).flatten() - assert(value.size == 1) + assert value.size == 1 value = value[0] if self.train: @@ -123,14 +122,12 @@ def _add_value(self, value, name, rel_tol, abs_tol, db=None, err_name=None): else: self.assert_allclose(value, db[name], err_name, rel_tol, abs_tol) - def assert_allclose(self, actual, reference, name, rel_tol, abs_tol): msg = "Failed value for: {}".format(name) numpy.testing.assert_allclose(actual, reference, rtol=rel_tol, atol=abs_tol, err_msg=msg) - def _add_values(self, values, *args, **kwargs): - '''Add values in special value format''' + """Add values in special value format""" values = numpy.atleast_1d(values) values = values.flatten() for val in values: @@ -138,22 +135,21 @@ def _add_values(self, values, *args, **kwargs): def _add_dict(self, d, dict_name, rel_tol, abs_tol): """Add all values in a dictionary in sorted key order""" - + if self.train: self.db[dict_name] = {} for key in sorted(d.keys()): - + # if msg is None: # key_msg = key - key_msg = dict_name+': '+key - + key_msg = dict_name + ": " + key # if isinstance(d[key],dict): # self._add_dict(d[key], dict_name, rel_tol, abs_tol) - + if type(d[key]) == bool: self._add_value(int(d[key]), key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) - + else: self._add_values(d[key], key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) diff --git a/baseclasses/BaseSolver.py b/baseclasses/BaseSolver.py index 456ff13..1070115 100644 --- a/baseclasses/BaseSolver.py +++ b/baseclasses/BaseSolver.py @@ -1,67 +1,8 @@ -from pprint import pprint as pp -#!/usr/local/bin/python -''' +""" BaseSolver Holds a basic Python Analysis Classes (base and inherited). - -Copyright (c) 2017 by Dr. Charles A. Mader -All rights reserved. Not to be used for commercial purposes. -Revision: 1.0 $Date: 01/06/2017 15:00$ - - -Developers: ------------ -- Dr. Charles A. Mader (CAM) - -History -------- - v. 1.0 - Initial Class Creation (CAM, 2013) - v. 2.0 - Major update to options implementation (CAM,2017) -''' - -__version__ = '$Revision: $' - - -# ============================================================================= -# Standard Python modules -# ============================================================================= - -# ============================================================================= -# Misc Definitions -# ============================================================================= - - -class CaseInsensitiveDict(dict): - def __setitem__(self, key, value): - super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) - - def __getitem__(self, key): - return super(CaseInsensitiveDict, self).__getitem__(key.lower()) - - def __contains__(self, key): - return super(CaseInsensitiveDict, self).__contains__(key.lower()) - - -class Error(Exception): - """ - Format the error message in a box to make it clear this - was a explicitly raised exception. - """ - - def __init__(self, message): - msg = '\n+'+'-'*78+'+'+'\n' + '| BaseSolver Error: ' - i = 19 - for word in message.split(): - if len(word) + i + 1 > 78: # Finish line and start new one - msg += ' '*(78-i)+'|\n| ' + word + ' ' - i = 1 + len(word)+1 - else: - msg += word + ' ' - i += len(word)+1 - msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n' - print(msg) - Exception.__init__(self) +""" # ============================================================================= @@ -69,16 +10,16 @@ def __init__(self, message): # ============================================================================= class BaseSolver(object): - ''' + """ Abstract Class for a basic Solver Object - ''' + """ def __init__(self, name, category={}, def_options={}, **kwargs): - ''' + """ Solver Class Initialization Documentation last updated: - ''' + """ # self.name = name @@ -92,18 +33,18 @@ def __init__(self, name, category={}, def_options={}, **kwargs): for key in self.defaultOptions: self.setOption(key, self.defaultOptions[key][1]) - koptions = kwargs.pop('options', CaseInsensitiveDict()) + koptions = kwargs.pop("options", CaseInsensitiveDict()) for key in koptions: self.setOption(key, koptions[key]) self.solverCreated = True - def __call__(self, *args, **kwargs): - ''' + def __call__(self, *args, **kwargs): + """ Run Analyzer (Calling Routine) Documentation last updated: - ''' + """ # Checks pass @@ -124,24 +65,23 @@ def setOption(self, name, value): try: self.defaultOptions[name] except KeyError: - Error("Option \'%-30s\' is not a valid %s option." % ( - name, self.name)) + Error("Option '%-30s' is not a valid %s option." % (name, self.name)) # Make sure we are not trying to change an immutable option if # we are not allowed to. if self.solverCreated and name in self.imOptions: - raise Error("Option '%-35s' cannot be modified after the solver " - "is created." % name) + raise Error("Option '%-35s' cannot be modified after the solver " "is created." % name) # Now we know the option exists, lets check if the type is ok: if isinstance(value, self.defaultOptions[name][0]): # Just set: self.options[name] = [type(value), value] else: - raise Error("Datatype for Option %-35s was not valid \n " - "Expected data type is %-47s \n " - "Received data type is %-47s" % ( - name, self.defaultOptions[name][0], type(value))) + raise Error( + "Datatype for Option %-35s was not valid \n " + "Expected data type is %-47s \n " + "Received data type is %-47s" % (name, self.defaultOptions[name][0], type(value)) + ) def getOption(self, name): """ @@ -161,16 +101,16 @@ def getOption(self, name): if name.lower() in self.defaultOptions: return self.options[name.lower()][1] else: - raise Error('%s is not a valid option name.' % name) + raise Error("%s is not a valid option name." % name) def printCurrentOptions(self): """ Prints a nicely formatted dictionary of all the current solver options to the stdout on the root processor""" if self.comm.rank == 0: - print('+---------------------------------------+') - print('| All %s Options: |' % self.name) - print('+---------------------------------------+') + print("+---------------------------------------+") + print("| All %s Options: |" % self.name) + print("+---------------------------------------+") # Need to assemble a temporary dictionary tmpDict = {} for key in self.options: @@ -183,9 +123,9 @@ def printModifiedOptions(self): options that have been modified from the defaults to the root processor""" if self.comm.rank == 0: - print('+---------------------------------------+') - print('| All Modified %s Options: |' % self.name) - print('+---------------------------------------+') + print("+---------------------------------------+") + print("| All Modified %s Options: |" % self.name) + print("+---------------------------------------+") # Need to assemble a temporary dictionary tmpDict = {} for key in self.options: @@ -197,10 +137,10 @@ def printModifiedOptions(self): # ============================================================================== # Optimizer Test # ============================================================================== -if __name__ == '__main__': +if __name__ == "__main__": - print('Testing ...') + print("Testing ...") # Test Optimizer - azr = BaseSolver('Test') + azr = BaseSolver("Test") dir(azr) diff --git a/baseclasses/ICAOAtmosphere.py b/baseclasses/ICAOAtmosphere.py index c3bfa5d..b658b8b 100644 --- a/baseclasses/ICAOAtmosphere.py +++ b/baseclasses/ICAOAtmosphere.py @@ -1,27 +1,14 @@ -""" -ICAOAtmosphere.py - -Developers: ------------ -- Dr. Gaetan K. W. Kenway (GKWK) -- Dr. Charles A. Mader (CAM) - -History -------- - v. 0.1 - Split atmosphere out of AeroProblem (CAM, 2014) -""" import numpy -import warnings -class ICAOAtmosphere(object): +class ICAOAtmosphere(object): def __init__(self, **kwargs): - + # Check if we have english units: self.englishUnits = False - if 'englishUnits' in kwargs: - self.englishUnits = kwargs['englishUnits'] - + if "englishUnits" in kwargs: + self.englishUnits = kwargs["englishUnits"] + def __call__(self, altitude): """ Compute the atmospheric properties at altitude, 'altitude' in meters @@ -31,124 +18,129 @@ def __call__(self, altitude): # Convert altitude to km since this is what the ICAO # atmosphere uses: if self.englishUnits: - altitude = altitude * .3048 / 1000.0 + altitude = altitude * 0.3048 / 1000.0 else: altitude = altitude / 1000.0 - K = 34.163195 - R0 = 6356.766 # Radius of Earth - H = altitude/(1.0 + altitude/R0) + K = 34.163195 + R0 = 6356.766 # Radius of Earth + H = altitude / (1.0 + altitude / R0) # Smoothing region on either side. 0.1 is 100m, seems to work - # well. Please don't change. + # well. Please don't change. dH_smooth = 0.1 # Sea Level Values - P0 = 101325 # Pressure + P0 = 101325 # Pressure # The set of break-points in the altitude (in km) H_break = numpy.array([11, 20, 32, 47, 51, 71, 84.852]) def hermite(t, p0, m0, p1, m1): """Compute a standard cubic hermite interpolant""" - return p0*(2*t**3 - 3*t**2 + 1) + m0*(t**3 - 2*t**2 + t) + \ - p1*(-2*t**3 + 3*t**2) + m1*(t**3-t**2) + return ( + p0 * (2 * t ** 3 - 3 * t ** 2 + 1) + + m0 * (t ** 3 - 2 * t ** 2 + t) + + p1 * (-2 * t ** 3 + 3 * t ** 2) + + m1 * (t ** 3 - t ** 2) + ) def getTP(H, index): """Compute temperature and pressure""" if index == 0: - T = 288.15 - 6.5*H - PP = (288.15/T)**(-K/6.5) + T = 288.15 - 6.5 * H + PP = (288.15 / T) ** (-K / 6.5) elif index == 1: T = 216.65 - PP = 0.22336*numpy.exp(-K*(H-11)/216.65) + PP = 0.22336 * numpy.exp(-K * (H - 11) / 216.65) elif index == 2: - T = 216.65 + (H-20) - PP = 0.054032*(216.65/T)**K + T = 216.65 + (H - 20) + PP = 0.054032 * (216.65 / T) ** K elif index == 3: - T = 228.65 + 2.8*(H-32) - PP = 0.0085666*(228.65/T)**(K/2.8) + T = 228.65 + 2.8 * (H - 32) + PP = 0.0085666 * (228.65 / T) ** (K / 2.8) elif index == 4: T = 270.65 - PP = 0.0010945*numpy.exp(-K*(H-47)/270.65) + PP = 0.0010945 * numpy.exp(-K * (H - 47) / 270.65) elif index == 5: - T = 270.65 - 2.8*(H-51) - PP = 0.00066063*(270.65/T)**(-K/2.8) + T = 270.65 - 2.8 * (H - 51) + PP = 0.00066063 * (270.65 / T) ** (-K / 2.8) elif index == 6: - T = 214.65 - 2*(H-71) - PP = 0.000039046*(214.65/T)**(-K/2) - + T = 214.65 - 2 * (H - 71) + PP = 0.000039046 * (214.65 / T) ** (-K / 2) + return T, PP # Determine if we need to do smoothing or not: smooth = False for index in range(len(H_break)): - if (numpy.real(H) > H_break[index] - dH_smooth and - numpy.real(H) < H_break[index] + dH_smooth): + if numpy.real(H) > H_break[index] - dH_smooth and numpy.real(H) < H_break[index] + dH_smooth: smooth = True break if not smooth: - index = H_break.searchsorted(H, side='left') + index = H_break.searchsorted(H, side="left") # Get the nominal values we need T, PP = getTP(H, index) else: H0 = H_break[index] - # Parametric distance along smoothing region + # Parametric distance along smoothing region H_left = H0 - dH_smooth H_right = H0 + dH_smooth - - t = (H - H_left)/(H_right-H_left) # Parametric value from 0 to 1 + + t = (H - H_left) / (H_right - H_left) # Parametric value from 0 to 1 # set an FD step to compute the derivs - - dh_FD = 1.e-4 #confirmed with stepsize study do not change from 1e-4 + + dh_FD = 1.0e-4 # confirmed with stepsize study do not change from 1e-4 # Compute slope and values at the left boundary TL, PPL = getTP(H_left, index) Tph, PPph = getTP(H_left + dh_FD, index) Tmh, PPmh = getTP(H_left - dh_FD, index) - + T_left = TL PP_left = PPL - - T_slope_left = (Tph-Tmh)/(2*dh_FD) * (dH_smooth*2) - PP_slope_left = (PPph-PPmh)/(2*dh_FD) * (dH_smooth*2) + + T_slope_left = (Tph - Tmh) / (2 * dh_FD) * (dH_smooth * 2) + PP_slope_left = (PPph - PPmh) / (2 * dh_FD) * (dH_smooth * 2) # Compute slope and values at the right boundary - TR, PPR = getTP(H_right, index+1) - Tph, PPph = getTP(H_right + dh_FD, index+1) - Tmh, PPmh = getTP(H_right - dh_FD, index+1) + TR, PPR = getTP(H_right, index + 1) + Tph, PPph = getTP(H_right + dh_FD, index + 1) + Tmh, PPmh = getTP(H_right - dh_FD, index + 1) T_right = TR PP_right = PPR - T_slope_right = (Tph-Tmh)/(2*dh_FD) * (dH_smooth*2) - PP_slope_right = (PPph-PPmh)/(2*dh_FD) * (dH_smooth*2) + T_slope_right = (Tph - Tmh) / (2 * dh_FD) * (dH_smooth * 2) + PP_slope_right = (PPph - PPmh) / (2 * dh_FD) * (dH_smooth * 2) # Standard cubic hermite spline interpolation T = hermite(t, T_left, T_slope_left, T_right, T_slope_right) - PP = hermite(t, PP_left, PP_slope_left, PP_right, PP_slope_right) + PP = hermite(t, PP_left, PP_slope_left, PP_right, PP_slope_right) # end if - P = P0*PP # Pressure + P = P0 * PP # Pressure if self.englishUnits: - P /= 47.88020833333 + P /= 47.88020833333 T *= 1.8 return P, T -#============================================================================== + + +# ============================================================================== # Analysis Test -#============================================================================== -if __name__ == '__main__': - print('Testing ...') - +# ============================================================================== +if __name__ == "__main__": + print("Testing ...") + Atm = ICAOAtmosphere() print(Atm(11000.001)) R = 287.870 - P,T = Atm(457.2) - rho = P/(R*T) - print(P,T,rho) + P, T = Atm(457.2) + rho = P / (R * T) + print(P, T, rho) diff --git a/baseclasses/pyAero_geometry.py b/baseclasses/pyAero_geometry.py index f700c32..b1799d7 100644 --- a/baseclasses/pyAero_geometry.py +++ b/baseclasses/pyAero_geometry.py @@ -1,86 +1,55 @@ -#!/usr/local/bin/python -''' +""" pyAero_geometry Holds the Python Aerodynamic Analysis Classes (base and inherited). - -Copyright (c) 2008 by Dr. Ruben E. Perez -All rights reserved. Not to be used for commercial purposes. -Revision: 1.1 $Date: 21/05/2008 21:00$ - - -Developers: ------------ -- Dr. Ruben E. Perez (RP) - -History -------- - v. 1.0 - Initial Class Creation (RP, 2008) -''' - -__version__ = '$Revision: $' - -''' -To Do: - - -''' - -# ============================================================================= -# Standard Python modules -# ============================================================================= -import os, sys -import pdb - -# ============================================================================= -# External Python modules -# ============================================================================= -import numpy - -# ============================================================================= -# Extension modules -# ============================================================================= - - -# ============================================================================= -# Misc Definitions -# ============================================================================= - +""" # ============================================================================= # Geometry Class # ============================================================================= class Geometry(object): - - ''' + + """ Abstract Class for Geometry Object - ''' - - def __init__(self, name={},CGPercent = 0.25,ForeSparPercent = 0.25, - RearSparPercent = 0.75,StaticMarginPercent=0.05, - ForeThickCon = 0.01, RearThickCon = 0.99, - rootOffset = 0.01, tipOffset=0.01, - xRootec=0.0, yRootec=0.0, zRootec=0.0, - *args, **kwargs): - - ''' + """ + + def __init__( + self, + name={}, + CGPercent=0.25, + ForeSparPercent=0.25, + RearSparPercent=0.75, + StaticMarginPercent=0.05, + ForeThickCon=0.01, + RearThickCon=0.99, + rootOffset=0.01, + tipOffset=0.01, + xRootec=0.0, + yRootec=0.0, + zRootec=0.0, + *args, + **kwargs + ): + + """ Flow Class Initialization - + Keyword Arguments: ------------------ name -> STRING: Geometry Instance Name - + Attributes: ----------- - - + + Documentation last updated: May. 21, 2008 - Ruben E. Perez - ''' - - # + """ + + # self.name = name self.CGPercent = CGPercent - self.ForeSparPercent = ForeSparPercent + self.ForeSparPercent = ForeSparPercent self.RearSparPercent = RearSparPercent self.StaticMarginPercent = StaticMarginPercent self.ForeThickCon = ForeThickCon @@ -90,62 +59,59 @@ def __init__(self, name={},CGPercent = 0.25,ForeSparPercent = 0.25, self.xRootec = xRootec self.yRootec = yRootec self.zRootec = zRootec - + def ListAttributes(self): - - ''' + + """ Print Structured Attributes List - + Documentation last updated: May. 21, 2008 - Ruben E. Perez - ''' - + """ + ListAttributes(self) - - + def __str__(self): - - ''' + + """ Print Structured List of Variable - + Documentation last updated: May. 21, 2008 - Ruben E. Perez - ''' - - return ('name \n'+' '+str(self.name).center(9) ) - + """ + + return "name \n" + " " + str(self.name).center(9) -#============================================================================== -# -#============================================================================== +# ============================================================================== +# +# ============================================================================== def ListAttributes(self): - - ''' - Print Structured Attributes List - - Documentation last updated: March. 24, 2008 - Ruben E. Perez - ''' - - print('\n') - print('Attributes List of: ' + repr(self.__dict__['name']) + ' - ' + self.__class__.__name__ + ' Instance\n') - self_keys = self.__dict__.keys() - self_keys.sort() - for key in self_keys: - if key != 'name': - print(str(key) + ' : ' + repr(self.__dict__[key])) - #end - #end - print('\n') - - - -#============================================================================== + + """ + Print Structured Attributes List + + Documentation last updated: March. 24, 2008 - Ruben E. Perez + """ + + print("\n") + print("Attributes List of: " + repr(self.__dict__["name"]) + " - " + self.__class__.__name__ + " Instance\n") + self_keys = self.__dict__.keys() + self_keys.sort() + for key in self_keys: + if key != "name": + print(str(key) + " : " + repr(self.__dict__[key])) + # end + # end + print("\n") + + +# ============================================================================== # Flow Test -#============================================================================== -if __name__ == '__main__': - - print('Testing ...') - +# ============================================================================== +if __name__ == "__main__": + + print("Testing ...") + # Test Variable - geo = Geometry(name = 'test') + geo = Geometry(name="test") geo.ListAttributes() print(geo) diff --git a/baseclasses/pyLG_problem.py b/baseclasses/pyLG_problem.py index b8abbca..96964da 100644 --- a/baseclasses/pyLG_problem.py +++ b/baseclasses/pyLG_problem.py @@ -1,55 +1,20 @@ -''' +""" pyLG_problem Holds the information to setup a single LG problem. -Right now this is a relatively simple class that computes the load conditions at +Right now this is a relatively simple class that computes the load conditions at the wheel for the main landing gear and an effective max-g load for further structural computation. Nothing in this computation can be a design variable right now because the beam model setup in TACS has no provision for changing the load as a design variable. We will assume a fixed aircraft mass and LG characteristics. The output load can change as a function of the LG geometry if necessary. -Copyright (c) 2019 by Dr. Charles A. Mader -All rights reserved. Not to be used for commercial purposes. -Revision: 1.0 $Date: 12/02/2019 21:00$ - - -Developers: ------------ -- Dr. Charles A. Mader (CM) - -History -------- - v. 1.0 - Initial Class Creation (CM, 2019) - -''' - -import sys, numpy, copy -import warnings -#from pygeo import geo_utils - -class Error(Exception): - """ - Format the error message in a box to make it clear this - was a expliclty raised exception. - """ - def __init__(self, message): - msg = '\n+'+'-'*78+'+'+'\n' + '| LGProblem Error: ' - i = 23 - for word in message.split(): - if len(word) + i + 1 > 78: # Finish line and start new one - msg += ' '*(78-i)+'|\n| ' + word + ' ' - i = 1 + len(word)+1 - else: - msg += word + ' ' - i += len(word)+1 - msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n' - print(msg) - Exception.__init__(self) +""" +import numpy class LGProblem(object): - ''' + """ Landing Gear Problem Object: This Landing Gear Problem Object should contain all of the information @@ -58,28 +23,27 @@ class LGProblem(object): Parameters ---------- - + name : str A name for the configuration - + evalFuncs : iteratble object containing strings - The names of the functions the user wants evaluated for this weight + The names of the functions the user wants evaluated for this weight problem - ''' + """ - def __init__(self, name, **kwargs): + def __init__(self, name, **kwargs): """ Initialize the mission problem """ - self.name=name - #self.units = units.lower() + self.name = name + # self.units = units.lower() # These are the parameters that can be simply set directly in # the class. - paras = set(('aircraftMass', 'tireEff', 'tireDef', 'shockEff', 'shockDef', - 'weightCondition','loadCaseType')) + paras = set(("aircraftMass", "tireEff", "tireDef", "shockEff", "shockDef", "weightCondition", "loadCaseType")) - self.g = 9.81 #(m/s) + self.g = 9.81 # (m/s) self.nMainGear = 2 # By default everything is None for para in paras: @@ -89,65 +53,61 @@ def __init__(self, name, **kwargs): for key in kwargs: if key in paras: setattr(self, key, kwargs[key]) - + # for key in paras: # print(key,getattr(self,key)) - if self.weightCondition.lower()=='mtow': - self.V_vert=3.048 #m/s or 10 fps - elif self.weightCondition.lower()=='mlw': - self.V_vert=1.83 # m/s or 6 fps + if self.weightCondition.lower() == "mtow": + self.V_vert = 3.048 # m/s or 10 fps + elif self.weightCondition.lower() == "mlw": + self.V_vert = 1.83 # m/s or 6 fps else: - print('Unrecognized weightCondition:',self.weightCondition) + print("Unrecognized weightCondition:", self.weightCondition) + + self.name += "_" + self.loadCaseType + "_" + self.weightCondition - self.name+='_'+self.loadCaseType+'_'+self.weightCondition - # Check for function list: self.evalFuncs = set() - if 'evalFuncs' in kwargs: - self.evalFuncs = set(kwargs['evalFuncs']) - + if "evalFuncs" in kwargs: + self.evalFuncs = set(kwargs["evalFuncs"]) def _computeLGForces(self): # These equations are from Aircraft Loading and Structural Layout by Denis Howe - - - f_stat = self.aircraftMass * self.g/self.nMainGear + f_stat = self.aircraftMass * self.g / self.nMainGear + + g_load = self.V_vert ** 2 / ( + self.nMainGear * self.g * (self.tireEff * self.tireDef + self.shockEff * self.shockDef) + ) - g_load = self.V_vert**2 /(self.nMainGear * self.g * \ - (self.tireEff * self.tireDef + self.shockEff * self.shockDef)) - # f_dyn = self.aircraftMass * self.V_vert**2 /\ # (4.0 * (self.tireEff * self.tireDef + self.shockEff * self.shockDef)) - f_dyn = (1.0/self.nMainGear)*g_load*self.aircraftMass * self.g + f_dyn = (1.0 / self.nMainGear) * g_load * self.aircraftMass * self.g - - return f_stat,f_dyn,g_load + return f_stat, f_dyn, g_load def getLoadFactor(self): - ''' + """ return the load factor for this load case - ''' - f_stat,f_dyn,g_load =self._computeLGForces() - if self.loadCaseType.lower()=='braking': + """ + f_stat, f_dyn, g_load = self._computeLGForces() + if self.loadCaseType.lower() == "braking": loadFactor = 1.0 - elif self.loadCaseType.lower()=='landing': + elif self.loadCaseType.lower() == "landing": loadFactor = g_load else: - print('Unrecognized loadCaseType:',self.loadCaseType) - + print("Unrecognized loadCaseType:", self.loadCaseType) return loadFactor def getLoadCaseArrays(self): - f_stat,f_dyn,g_load =self._computeLGForces() - - if self.loadCaseType.lower()=='braking': + f_stat, f_dyn, g_load = self._computeLGForces() + + if self.loadCaseType.lower() == "braking": nCondition = 2 - if self.weightCondition.lower()=='mlw': + if self.weightCondition.lower() == "mlw": fVert = numpy.zeros(nCondition) fVert[0] = 0.6 * f_stat fVert[1] = 0.6 * f_stat @@ -158,7 +118,7 @@ def getLoadCaseArrays(self): fSide = numpy.zeros(nCondition) - elif self.weightCondition.lower()=='mtow': + elif self.weightCondition.lower() == "mtow": fVert = numpy.zeros(nCondition) fVert[0] = 0.5 * f_stat fVert[1] = 0.5 * f_stat @@ -170,9 +130,9 @@ def getLoadCaseArrays(self): fSide = numpy.zeros(nCondition) else: - print('Unrecognized weightCondition:',self.weightCondition) - - elif self.loadCaseType.lower()=='landing': + print("Unrecognized weightCondition:", self.weightCondition) + + elif self.loadCaseType.lower() == "landing": nCondition = 5 fVert = numpy.zeros(nCondition) fVert[0] = f_dyn @@ -180,27 +140,23 @@ def getLoadCaseArrays(self): fVert[2] = 0.75 * f_dyn fVert[3] = 0.5 * f_dyn fVert[4] = 0.5 * f_dyn - + fDrag = numpy.zeros(nCondition) fDrag[0] = 0.25 * f_dyn fDrag[1] = 0.4 * f_dyn fDrag[2] = 0.4 * f_dyn fDrag[3] = 0.0 fDrag[4] = 0.0 - + # sign convention here is that negative is inboard facing fSide = numpy.zeros(nCondition) fSide[0] = 0 fSide[1] = -0.25 * f_dyn - fSide[2] = 0.25 * f_dyn + fSide[2] = 0.25 * f_dyn fSide[3] = -0.4 * f_dyn - fSide[4] = 0.3 * f_dyn - - else: - print('Unrecognized loadCaseType:',self.loadCaseType) - - + fSide[4] = 0.3 * f_dyn + else: + print("Unrecognized loadCaseType:", self.loadCaseType) - return fVert,fDrag,fSide - + return fVert, fDrag, fSide diff --git a/baseclasses/pyTransi_problem.py b/baseclasses/pyTransi_problem.py index 65c6472..53391df 100644 --- a/baseclasses/pyTransi_problem.py +++ b/baseclasses/pyTransi_problem.py @@ -1,60 +1,20 @@ """ pyAero_problem - -Developers: ------------ -- Dr. Gaetan K. W. Kenway (GKWK) - -History -------- - v. 0.1 - Complete overall of AeroProblem (GKWK, 2014) """ +from .utils import Error + -# ============================================================================= -# Imports -# ============================================================================= -import numpy -import warnings -from .ICAOAtmosphere import ICAOAtmosphere - -class CaseInsensitiveDict(dict): - def __setitem__(self, key, value): - super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) - - def __getitem__(self, key): - return super(CaseInsensitiveDict, self).__getitem__(key.lower()) - -class Error(Exception): - """ - Format the error message in a box to make it clear this - was a expliclty raised exception. - """ - def __init__(self, message): - msg = '\n+'+'-'*78+'+'+'\n' + '| AeroProblem Error: ' - i = 20 - for word in message.split(): - if len(word) + i + 1 > 78: # Finish line and start new one - msg += ' '*(78-i)+'|\n| ' + word + ' ' - i = 1 + len(word)+1 - else: - msg += word + ' ' - i += len(word)+1 - msg += ' '*(78-i) + '|\n' + '+'+'-'*78+'+'+'\n' - print(msg) - Exception.__init__(self) - class TransiProblem(object): def __init__(self, name, **kwargs): # Always have to have the name # Always have to have the name self.name = name # These are the parameters that can be simply set directly in - # the class. - paras = set(('mach', 'reynolds', 'T', - 'nCritTS', 'nCritCF','spanDirection','sectionData','partName')) + # the class. + paras = set(("mach", "reynolds", "T", "nCritTS", "nCritCF", "spanDirection", "sectionData", "partName")) # By default everything is None - #print(kwargs) + # print(kwargs) for para in paras: setattr(self, para, None) @@ -62,45 +22,44 @@ def __init__(self, name, **kwargs): for key in kwargs: if key in paras: setattr(self, key, kwargs[key]) - #print (key) - #print(self.mach,'self.name') - + # print (key) + # print(self.mach,'self.name') # these are the possible input values - possibleInputStates = set(['mach', 'reynolds', 'T', - 'nCritTS', 'nCritCF','spanDirection','sectionData','partName']) + possibleInputStates = set( + ["mach", "reynolds", "T", "nCritTS", "nCritCF", "spanDirection", "sectionData", "partName"] + ) # turn the kwargs into a set keys = set(kwargs.keys()) - #print(keys) + # print(keys) # save the initials states self.inputs = {} for key in keys: if key in possibleInputStates: - self.inputs[key]= kwargs[key] - #print(key,self.inputs[key]) - #print(kwargs.keys(),'self.name') + self.inputs[key] = kwargs[key] + # print(key,self.inputs[key]) + # print(kwargs.keys(),'self.name') # full list of states in the class - self.fullState = set(['mach', 'reynolds', 'T', - 'nCritTS', 'nCritCF','spanDirection','sectionData','partName']) + self.fullState = set( + ["mach", "reynolds", "T", "nCritTS", "nCritCF", "spanDirection", "sectionData", "partName"] + ) # now call the routine to setup the states self._setStates(self.inputs) - #print(self.phi_le) + # print(self.phi_le) - - - def _setStates(self,inputDict): - ''' + def _setStates(self, inputDict): + """ Take in a dictionary and set up the full set of states. - ''' + """ # Now we can do the name matching for the data for the # thermodynamic condition. We actually can work backwards from # the list given in the doc string. - + for key in self.fullState: self.__dict__[key] = None @@ -112,28 +71,26 @@ def _setStates(self,inputDict): if key in self.inputs.keys(): pass else: - validKeys = '' + validKeys = "" for vkey in self.inputs: - validKeys += vkey+', ' + validKeys += vkey + ", " - raise Error('Invalid input parameter: %s . Only values initially specifed' - ' as inputs may be modifed. valid inputs include: %s'%(key,validKeys)) + raise Error( + "Invalid input parameter: %s . Only values initially specifed" + " as inputs may be modifed. valid inputs include: %s" % (key, validKeys) + ) # now we know our inputs are valid. update self.Input and update states for key in inputDict: - self.inputs[key]=inputDict[key] - if set(('mach', 'reynolds', 'T', 'nCritTS', 'nCritCF','spanDirection','sectionData','partName')) == inKeys: - self.__dict__['mach'] = self.inputs['mach'] - self.__dict__['reynolds'] = self.inputs['reynolds'] - self.__dict__['T'] = self.inputs['T'] - self.__dict__['nCritTS'] = self.inputs['nCritTS'] - self.__dict__['nCritCF'] = self.inputs['nCritCF'] - self.__dict__['spanDirection'] = self.inputs['spanDirection'] - self.__dict__['sectionData'] = self.inputs['sectionData'] - self.__dict__['partName'] = self.inputs['partName'] + self.inputs[key] = inputDict[key] + if set(("mach", "reynolds", "T", "nCritTS", "nCritCF", "spanDirection", "sectionData", "partName")) == inKeys: + self.__dict__["mach"] = self.inputs["mach"] + self.__dict__["reynolds"] = self.inputs["reynolds"] + self.__dict__["T"] = self.inputs["T"] + self.__dict__["nCritTS"] = self.inputs["nCritTS"] + self.__dict__["nCritCF"] = self.inputs["nCritCF"] + self.__dict__["spanDirection"] = self.inputs["spanDirection"] + self.__dict__["sectionData"] = self.inputs["sectionData"] + self.__dict__["partName"] = self.inputs["partName"] else: - raise Error('There shold be 14 parameters giiven') - - - - + raise Error("There shold be 14 parameters giiven") From bfbc81a5c551a8b822ba1fb61900a91c7758168b Mon Sep 17 00:00:00 2001 From: joanibal Date: Mon, 18 Jan 2021 17:03:03 -0500 Subject: [PATCH 15/21] merge prt3 --- baseclasses/BaseRegTest.py | 398 +++++++++++++++++++++++++------- baseclasses/BaseSolver.py | 218 +++++++++++------ baseclasses/pyLG_problem.py | 151 +++++++++--- baseclasses/pyTransi_problem.py | 1 + 4 files changed, 586 insertions(+), 182 deletions(-) diff --git a/baseclasses/BaseRegTest.py b/baseclasses/BaseRegTest.py index 1bd34f7..5353439 100644 --- a/baseclasses/BaseRegTest.py +++ b/baseclasses/BaseRegTest.py @@ -1,24 +1,57 @@ -from __future__ import print_function -from mpi4py import MPI +try: + from mpi4py import MPI +except ImportError: + print("Warning: unable to find mpi4py. Parallel regression tests will cause errors") import numpy import os -import pprint +import sys +import json +from collections import deque +from contextlib import contextmanager + + +def getTol(**kwargs): + """ + Returns the tolerances based on kwargs. + There are two ways of specifying tolerance: + 1. pass in "tol" which will set atol = rtol = tol + 2. individually set atol and rtol. + If any values are unspecified, the default value will be used. + """ + DEFAULT_TOL = 1e-12 + if "tol" in kwargs: + rtol = kwargs["tol"] + atol = kwargs["tol"] + else: + if "rtol" in kwargs: + rtol = kwargs["rtol"] + else: + rtol = DEFAULT_TOL + if "atol" in kwargs: + atol = kwargs["atol"] + else: + atol = DEFAULT_TOL + return rtol, atol class BaseRegTest(object): - def __init__(self, ref, train=False, comm=None, check_arch=False): - # self.ref_file = ref_file - - self.setRef(ref) - self.train = train - + def __init__(self, ref_file, train=False, comm=None, check_arch=False): + self.ref_file = ref_file if comm is not None: self.comm = comm else: self.comm = MPI.COMM_WORLD - self.rank = self.comm.rank + self.train = train + if self.train: + self.db = {} + else: + # We need to check here that the reference file exists, otherwise + # it will hang when it tries to open it on the root proc. + assert os.path.isfile(self.ref_file) + self.db = self.readRef() + # dictionary of real/complex PETSc arch names self.arch = {"real": None, "complex": None} # If we specify the test type, verify that the $PETSC_ARCH contains 'real' or 'complex', @@ -30,17 +63,25 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - # self.save() - pass - - def setRef(self, ref): - self.db = ref + if self.train: + self.writeRef() def getRef(self): return self.db - def setMode(self, train=False): - self.train = train + def writeRef(self): + with multi_proc_exception_check(self.comm): + if self.rank == 0: + writeRefJSON(self.ref_file, self.db) + + def readRef(self): + with multi_proc_exception_check(self.comm): + if self.rank == 0: + db = readRefJSON(self.ref_file) + else: + db = None + db = self.comm.bcast(db) + return db def checkPETScArch(self): # Determine real/complex petsc arches: take the one when the script is @@ -71,85 +112,276 @@ def root_print(self, s): print(s) # Add values from root only - def root_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): - """Add values but only on the root proc""" - if self.rank == 0: - self._add_values(values, name, rel_tol, abs_tol) - - def root_add_dict(self, d, name, rel_tol=1e-12, abs_tol=1e-12): - """Only write from the root proc""" - if self.rank == 0: - self._add_dict(d, name, rel_tol, abs_tol) + def root_add_val(self, name, values, **kwargs): + """ + Add values but only on the root proc + """ + with multi_proc_exception_check(self.comm): + if self.rank == 0: + self._add_values(name, values, **kwargs) + + def root_add_dict(self, name, d, **kwargs): + """ + Only write from the root proc + """ + with multi_proc_exception_check(self.comm): + if self.rank == 0: + self._add_dict(name, d, name, **kwargs) # Add values from all processors - def par_add_val(self, values, name, rel_tol=1e-12, abs_tol=1e-12): - """Add value(values) from parallel process in sorted order""" + def par_add_val(self, name, values, **kwargs): + """ + Add value(values) from parallel process in sorted order + """ values = self.comm.gather(values) - if self.rank == 0: - for i in range(len(values)): - print("Value(s) on processor: %d" % i) - self._add_values(values[i], name, rel_tol, abs_tol) - - def par_add_sum(self, values, name, rel_tol=1e-12, abs_tol=1e-12): - """Add the sum of sum of the values from all processors.""" + with multi_proc_exception_check(self.comm): + if self.rank == 0: + self._add_values(name, values, **kwargs) + + def par_add_sum(self, name, values, **kwargs): + """ + Add the sum of sum of the values from all processors. + """ reducedSum = self.comm.reduce(numpy.sum(values)) - if self.rank == 0: - self._add_value(reducedSum, name, rel_tol, abs_tol) - - def par_add_norm(self, values, name, rel_tol=1e-12, abs_tol=1e-12): - """Add the norm across values from all processors.""" + with multi_proc_exception_check(self.comm): + if self.rank == 0: + self._add_values(name, reducedSum, **kwargs) + + def par_add_norm(self, name, values, **kwargs): + """ + Add the norm across values from all processors. + """ reducedSum = self.comm.reduce(numpy.sum(values ** 2)) - if self.rank == 0: - self._add_value(numpy.sqrt(reducedSum), name, rel_tol, abs_tol) + with multi_proc_exception_check(self.comm): + if self.rank == 0: + self._add_values(name, numpy.sqrt(reducedSum), **kwargs) # ***************** # Private functions # ***************** - def _add_value(self, value, name, rel_tol, abs_tol, db=None, err_name=None): - # We only check floats and integers - if db == None: + def assert_allclose(self, actual, reference, name, rtol, atol, full_name=None): + if full_name is None: + full_name = name + msg = "Failed value for: {}".format(full_name) + numpy.testing.assert_allclose(actual, reference, rtol=rtol, atol=atol, err_msg=msg) + + def _add_values(self, name, values, db=None, **kwargs): + """ + Add values in special value format + If compare=True, it will compare the supplied value against an existing value + in the database instead of adding the value, even in training mode. This is useful + for example in dot product tests when comparing two values. + """ + rtol, atol = getTol(**kwargs) + compare = kwargs["compare"] if "compare" in kwargs else False + full_name = kwargs["full_name"] if "full_name" in kwargs else None + if db is None: db = self.db - - if err_name == None: - err_name = name - - value = numpy.atleast_1d(value).flatten() - assert value.size == 1 - - value = value[0] - if self.train: - db[name] = value + if not self.train or (self.train and compare): + self.assert_allclose(values, db[name], name, rtol, atol, full_name) else: - self.assert_allclose(value, db[name], err_name, rel_tol, abs_tol) - - def assert_allclose(self, actual, reference, name, rel_tol, abs_tol): - msg = "Failed value for: {}".format(name) - numpy.testing.assert_allclose(actual, reference, rtol=rel_tol, atol=abs_tol, err_msg=msg) - - def _add_values(self, values, *args, **kwargs): - """Add values in special value format""" - values = numpy.atleast_1d(values) - values = values.flatten() - for val in values: - self._add_value(val, *args, **kwargs) - - def _add_dict(self, d, dict_name, rel_tol, abs_tol): - """Add all values in a dictionary in sorted key order""" - + if name in db.keys(): + raise ValueError( + "The name {} is already in the training database. Please give values UNIQUE keys.".format(name) + ) + if isinstance(values, numpy.ndarray): + db[name] = values.copy() + else: + db[name] = values + + def _add_dict(self, dict_name, d, full_name, db=None, **kwargs): + """ + Add all values in a dictionary in sorted key order + """ + rtol, atol = getTol(**kwargs) + if db is None: + db = self.db if self.train: - self.db[dict_name] = {} + db[dict_name] = {} + elif dict_name not in db.keys(): + raise ValueError(f"The key '{dict_name}' was not found in the reference file!") for key in sorted(d.keys()): + full_name = f"{full_name}: {key}" + if isinstance(d[key], bool): + self._add_values(key, int(d[key]), rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name) + elif isinstance(d[key], dict): + # do some good ol' fashion recursion + self._add_dict(key, d[key], full_name, rtol=rtol, atol=atol, db=db[dict_name]) + else: + self._add_values(key, d[key], rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name) + + +# ============================================================================= +# reference files I/O +# ============================================================================= + +# based on this stack overflow answer https://stackoverflow.com/questions/3488934/simplejson-and-numpy-array/24375113#24375113 +def writeRefJSON(file_name, ref): + class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + """If input object is an ndarray it will be converted into a dict + holding dtype, shape and the data, base64 encoded. + """ + if isinstance(obj, numpy.ndarray): + if obj.flags["C_CONTIGUOUS"]: + pass + else: + obj = numpy.ascontiguousarray(obj) + assert obj.flags["C_CONTIGUOUS"] + if obj.size == 1: + return obj.item() + else: + return dict(__ndarray__=obj.tolist(), dtype=str(obj.dtype), shape=obj.shape) + elif isinstance(obj, numpy.integer): + return dict(__ndarray__=int(obj), dtype=str(obj.dtype), shape=obj.shape) + elif isinstance(obj, numpy.floating): + return dict(__ndarray__=float(obj), dtype=str(obj.dtype), shape=obj.shape) + + # Let the base class default method raise the TypeError + super(NumpyEncoder, self).default(obj) + + with open(file_name, "w") as json_file: + json.dump(ref, json_file, sort_keys=True, indent=4, separators=(",", ": "), cls=NumpyEncoder) + + +# based on this stack overflow answer https://stackoverflow.com/questions/3488934/simplejson-and-numpy-array/24375113#24375113 +def readRefJSON(file_name): + def json_numpy_obj_hook(dct): + """Decodes a previously encoded numpy ndarray with proper shape and dtype. + + :param dct: (dict) json encoded ndarray + :return: (ndarray) if input was an encoded ndarray + """ + if isinstance(dct, dict) and "__ndarray__" in dct: + data = dct["__ndarray__"] + return numpy.array(data, dct["dtype"]).reshape(dct["shape"]) + return dct + + with open(file_name, "r") as json_file: + data = json.load(json_file, object_hook=json_numpy_obj_hook) + + return data + + +def convertRegFileToJSONRegFile(file_name, output_file=None): + """ converts from the old format of regression test file to the new JSON format""" + + if output_file is None: + output_file = os.path.splitext(file_name)[0] + ".json" + + ref = {} + line_history = deque(maxlen=3) + + def saveValueInRef(value, key, mydict): + """a helper function to add values to our ref dict""" + + if key in mydict: + # turn the value into a numpy array and append or just append if + # the value is already an numpy.array + + if isinstance(mydict[key], numpy.ndarray): + mydict[key] = numpy.append(mydict[key], value) + else: + mydict[key] = numpy.array([mydict[key], value]) + else: + mydict[key] = value + + curr_dict = ref + with open(file_name, "r") as fid: + for line in fid: + # key ideas + # - lines starting with @value aren't added to the queque + + # check to see if it is the start of dictionary of values + if "Dictionary Key: " in line: + + # if there are other lines in the queque this isn't following + # an @value + if len(line_history) > 0: + + # We must create a new dictionary and add it to ref + last_line = line_history[-1].rstrip() + if "Dictionary Key: " in last_line: + # this is a nested dict + key = last_line[len("Dictionary Key: ") :] + + if len(line_history) > 1: + prior_dict = curr_dict + curr_dict[key] = {} + curr_dict = curr_dict[key] + else: + prior_dict[key] = {} + curr_dict = prior_dict[key] + print("nested dict", last_line) + else: + print("dict ", last_line) + ref[last_line] = {} + curr_dict = ref[last_line] + + if "@value" in line: + # get the value from the ref file + value = float(line.split()[1]) + + # if a value was not just added + if line_history: + # grab the data and use them as the keys for the reference dictionary + key = line_history[-1].rstrip() + if "Dictionary Key: " in key: + key = key[len("Dictionary Key: ") :] + else: + curr_dict = ref + + saveValueInRef(value, key, curr_dict) + line_history.clear() + else: + # When deque reaches 2 lines, will automatically evict oldest + line_history.append(line) + + writeRefJSON(output_file, ref) + + +"""This strategy of dealing with error propagation to multiple procs is taken directly form openMDAO.utils; +It was not imported and used here to avoid adding openMDAO as a dependency. If openMDAO is added as a dependency in +the future this context manager definition should be replaced by an import""" + + +@contextmanager +def multi_proc_exception_check(comm): + """ + Raise an exception on all procs if it is raised on one. + Wrap this around code that you want to globally fail if it fails + on any MPI process in comm. If not running under MPI, don't + handle any exceptions. + Parameters + ---------- + comm : MPI communicator or None + Communicator from the ParallelGroup that owns the calling solver. + """ + if MPI is None or comm is None or comm.size == 1: + yield + else: + try: + yield + except Exception: + exc = sys.exc_info() + + fail = 1 + else: + fail = 0 - # if msg is None: - # key_msg = key - key_msg = dict_name + ": " + key - - # if isinstance(d[key],dict): - # self._add_dict(d[key], dict_name, rel_tol, abs_tol) - - if type(d[key]) == bool: - self._add_value(int(d[key]), key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) - + failed = comm.allreduce(fail) + if failed: + if fail: + msg = f"{exc[1]}" + else: + msg = None + allmsgs = comm.allgather(msg) + if fail: + msg = f"Exception raised on rank {comm.rank}: {exc[1]}" + raise exc[0](msg).with_traceback(exc[2]) + raise exc[0](msg).with_traceback(exc[2]) else: - self._add_values(d[key], key, rel_tol, abs_tol, db=self.db[dict_name], err_name=key_msg) + for m in allmsgs: + if m is not None: + raise RuntimeError(f"Exception raised on other rank: {m}.") diff --git a/baseclasses/BaseSolver.py b/baseclasses/BaseSolver.py index 1070115..5fa2822 100644 --- a/baseclasses/BaseSolver.py +++ b/baseclasses/BaseSolver.py @@ -3,47 +3,95 @@ Holds a basic Python Analysis Classes (base and inherited). """ - +from difflib import get_close_matches +from pprint import pprint +from .utils import CaseInsensitiveDict, CaseInsensitiveSet, Error # ============================================================================= # BaseSolver Class # ============================================================================= class BaseSolver(object): - """ Abstract Class for a basic Solver Object """ - def __init__(self, name, category={}, def_options={}, **kwargs): + def __init__( + self, + name, + category, + defaultOptions={}, + options={}, + immutableOptions=set(), + deprecatedOptions={}, + comm=None, + informs={}, + checkDefaultOptions=True, + caseSensitiveOptions=False, + ): """ Solver Class Initialization - Documentation last updated: + Parameters + ---------- + name : str + The name of the solver + category : dict + The category of the solver + defaultOptions : dict, optional + The default options dictionary + options : dict, optional + The user-supplied options dictionary + immutableOptions : set of strings, optional + A set of immutable option names, which cannot be modified after solver creation. + deprecatedOptions : dict, optional + A dictionary containing deprecated option names, and a message to display if they were used. + comm : MPI Communicator, optional + The comm object to be used. If none, serial execution is assumed. + informs : dict, optional + A dictionary of exit code: exit message mappings. + checkDefaultOptions : bool, optional + A flag to specify whether the default options should be used for error checking. + This is used in cases where the default options are not the complete set, which is common for external solvers. + In such cases, no error checking is done when calling ``setOption``, but the default options are still set as options + upon solver creation. + caseSensitiveOptions : bool, optional + A flag to specify whether the option names are case sensitive or insensitive. """ - # self.name = name self.category = category - self.options = CaseInsensitiveDict() - self.defaultOptions = def_options + if not caseSensitiveOptions: + self.options = CaseInsensitiveDict() + self.defaultOptions = CaseInsensitiveDict(defaultOptions) + self.immutableOptions = CaseInsensitiveSet(immutableOptions) + self.deprecatedOptions = CaseInsensitiveDict(deprecatedOptions) + else: + self.options = {} + self.defaultOptions = defaultOptions + self.immutableOptions = immutableOptions + self.deprecatedOptions = deprecatedOptions + self.comm = comm + self.informs = informs self.solverCreated = False - self.imOptions = {} + self.checkDefaultOptions = checkDefaultOptions # Initialize Options - for key in self.defaultOptions: - self.setOption(key, self.defaultOptions[key][1]) + for key, (optionType, optionValue) in self.defaultOptions.items(): + # Check if the default is given in a list of possible values + if isinstance(optionValue, list) and optionType is not list: + # Default is the first element of the list + self.setOption(key, optionValue[0]) + else: + self.setOption(key, optionValue) - koptions = kwargs.pop("options", CaseInsensitiveDict()) - for key in koptions: - self.setOption(key, koptions[key]) + for key in options: + self.setOption(key, options[key]) self.solverCreated = True def __call__(self, *args, **kwargs): """ Run Analyzer (Calling Routine) - - Documentation last updated: """ # Checks @@ -56,32 +104,51 @@ def setOption(self, name, value): Parameters ---------- name : str - Name of option to set. Not case sensitive + Name of option to set. Not case sensitive. value : varies Value to set. Type is checked for consistency. """ - name = name.lower() - try: - self.defaultOptions[name] - except KeyError: - Error("Option '%-30s' is not a valid %s option." % (name, self.name)) + # Check if the option exists + if self.checkDefaultOptions: + try: + defaultType, defaultValue = self.defaultOptions[name] + except KeyError: + if name in self.deprecatedOptions: + raise Error(f"Option {name} is deprecated. {self.deprecatedOptions[name]}") + else: + guess = get_close_matches(name, list(self.defaultOptions.keys()), n=1, cutoff=0.0)[0] + raise Error(f"Option {name} is not a valid {self.name} option. Perhaps you meant {guess}?") # Make sure we are not trying to change an immutable option if # we are not allowed to. - if self.solverCreated and name in self.imOptions: - raise Error("Option '%-35s' cannot be modified after the solver " "is created." % name) - - # Now we know the option exists, lets check if the type is ok: - if isinstance(value, self.defaultOptions[name][0]): - # Just set: - self.options[name] = [type(value), value] + if self.solverCreated and name in self.immutableOptions: + raise Error(f"Option {name} cannot be modified after the solver is created.") + + if self.checkDefaultOptions: + # If the default provides a list of acceptable values, check whether the value is valid + if isinstance(defaultValue, list) and defaultType is not list: + if value in defaultValue: + self.options[name] = value + else: + raise Error( + f"Value for option {name} is not valid. " + + f"Value must be one of {defaultValue} with data type {defaultType}. " + + f"Received value is {value} with data type {type(value)}." + ) + else: + # If a list is not provided, check just the type + if isinstance(value, defaultType): + self.options[name] = value + else: + raise Error( + f"Datatype for option {name} is not valid. " + + f"Expected data type {defaultType}. " + + f"Received data type is {type(value)}." + ) else: - raise Error( - "Datatype for Option %-35s was not valid \n " - "Expected data type is %-47s \n " - "Received data type is %-47s" % (name, self.defaultOptions[name][0], type(value)) - ) + # no error checking + self.options[name] = value def getOption(self, name): """ @@ -98,49 +165,62 @@ def getOption(self, name): Return the current value of the option. """ - if name.lower() in self.defaultOptions: - return self.options[name.lower()][1] + if name in self.defaultOptions or not self.checkDefaultOptions: + if name in self.options: + return self.options[name] + else: + raise Error( + f"Option {name} was not found. " + + "Because options checking has been disabled, make sure the option has been set first." + ) else: - raise Error("%s is not a valid option name." % name) + guess = get_close_matches(name, list(self.defaultOptions.keys()), n=1, cutoff=0.0)[0] + raise Error(f"{name} is not a valid option name. Perhaps you meant {guess}?") def printCurrentOptions(self): """ Prints a nicely formatted dictionary of all the current solver - options to the stdout on the root processor""" - if self.comm.rank == 0: - print("+---------------------------------------+") - print("| All %s Options: |" % self.name) - print("+---------------------------------------+") - # Need to assemble a temporary dictionary - tmpDict = {} - for key in self.options: - tmpDict[key] = self.getOption(key) - pp(tmpDict) + options to the stdout on the root processor + """ + + self.pp("+----------------------------------------+") + self.pp("|" + f"All {self.name} Options:".center(40) + "|") + self.pp("+----------------------------------------+") + # Need to assemble a temporary dictionary + tmpDict = {} + for key in self.options: + tmpDict[key] = self.getOption(key) + self.pp(tmpDict) def printModifiedOptions(self): """ Prints a nicely formatted dictionary of all the current solver options that have been modified from the defaults to the root - processor""" - if self.comm.rank == 0: - print("+---------------------------------------+") - print("| All Modified %s Options: |" % self.name) - print("+---------------------------------------+") - # Need to assemble a temporary dictionary - tmpDict = {} - for key in self.options: - if self.getOption(key) != self.defaultOptions[key][1]: - tmpDict[key] = self.getOption(key) - pp(tmpDict) - - -# ============================================================================== -# Optimizer Test -# ============================================================================== -if __name__ == "__main__": - - print("Testing ...") - - # Test Optimizer - azr = BaseSolver("Test") - dir(azr) + processor + """ + self.pp("+----------------------------------------+") + self.pp("|" + f"All Modified {self.name} Options:".center(40) + "|") + self.pp("+----------------------------------------+") + # Need to assemble a temporary dictionary + tmpDict = {} + for key in self.options: + defaultType, defaultValue = self.defaultOptions[key] + if defaultType is list and not isinstance(defaultValue, list): + defaultValue = defaultValue[0] + optionValue = self.getOption(key) + if optionValue != defaultValue: + tmpDict[key] = optionValue + self.pp(tmpDict) + + def pp(self, obj): + """ + This method prints ``obj`` (via pprint) on the root proc of ``self.comm`` if it exists. + Otherwise it will just print ``obj``. + + Parameters + ---------- + obj : object + any Python object to be printed + """ + if (self.comm is not None and self.comm.rank == 0) or self.comm is None: + pprint(obj) diff --git a/baseclasses/pyLG_problem.py b/baseclasses/pyLG_problem.py index 96964da..73f7a9c 100644 --- a/baseclasses/pyLG_problem.py +++ b/baseclasses/pyLG_problem.py @@ -8,8 +8,8 @@ the beam model setup in TACS has no provision for changing the load as a design variable. We will assume a fixed aircraft mass and LG characteristics. The output load can change as a function of the LG geometry if necessary. - """ + import numpy @@ -41,7 +41,18 @@ def __init__(self, name, **kwargs): # These are the parameters that can be simply set directly in # the class. - paras = set(("aircraftMass", "tireEff", "tireDef", "shockEff", "shockDef", "weightCondition", "loadCaseType")) + paras = set( + ( + "aircraftMass", + "tireEff", + "tireDef", + "shockEff", + "shockDef", + "weightCondition", + "loadCaseType", + "loadFrac", + ) + ) self.g = 9.81 # (m/s) self.nMainGear = 2 @@ -57,15 +68,35 @@ def __init__(self, name, **kwargs): # for key in paras: # print(key,getattr(self,key)) - if self.weightCondition.lower() == "mtow": + if self.weightCondition.lower() == "mlw": self.V_vert = 3.048 # m/s or 10 fps - elif self.weightCondition.lower() == "mlw": + elif self.weightCondition.lower() == "mtow": self.V_vert = 1.83 # m/s or 6 fps else: print("Unrecognized weightCondition:", self.weightCondition) + if self.loadCaseType.lower() == "braking": + self.nCondition = 2 + self.nameList = ["Braked rolling", "Reversed Braking"] + elif self.loadCaseType.lower() == "landing": + self.nCondition = 7 + self.nameList = [ + "Drag and side load", + "Drag and side load", + "Drag and side load", + "Side load", + "Side load", + "High drag and spring-back", + "High drag and spring-back", + ] + + else: + print("Unrecognized loadCaseType:", self.loadCaseType) + self.name += "_" + self.loadCaseType + "_" + self.weightCondition + self.nameList = None + # Check for function list: self.evalFuncs = set() if "evalFuncs" in kwargs: @@ -76,22 +107,25 @@ def _computeLGForces(self): f_stat = self.aircraftMass * self.g / self.nMainGear - g_load = self.V_vert ** 2 / ( - self.nMainGear * self.g * (self.tireEff * self.tireDef + self.shockEff * self.shockDef) + g_load = (self.V_vert ** 2 + (2 * self.g * (1 - self.loadFrac) * (self.tireDef + self.shockDef))) / ( + 2.0 * self.g * (self.tireEff * self.tireDef + self.shockEff * self.shockDef) ) - + # print('gload',self.V_vert**2,(2*self.g*(1-self.loadFrac)*(self.tireDef+self.shockDef)),2.0 , self.g,self.tireEff * self.tireDef,self.shockEff * self.shockDef,self.V_vert**2 /(2.0 * self.g ), (self.tireEff * self.tireDef + self.shockEff * self.shockDef)) # f_dyn = self.aircraftMass * self.V_vert**2 /\ # (4.0 * (self.tireEff * self.tireDef + self.shockEff * self.shockDef)) f_dyn = (1.0 / self.nMainGear) * g_load * self.aircraftMass * self.g - return f_stat, f_dyn, g_load + f_sb = f_dyn + + # print('fdyn',f_stat,f_dyn,f_sb,g_load) + return f_stat, f_dyn, f_sb, g_load def getLoadFactor(self): """ return the load factor for this load case """ - f_stat, f_dyn, g_load = self._computeLGForces() + f_stat, f_dyn, f_sb, g_load = self._computeLGForces() if self.loadCaseType.lower() == "braking": loadFactor = 1.0 elif self.loadCaseType.lower() == "landing": @@ -103,60 +137,117 @@ def getLoadFactor(self): def getLoadCaseArrays(self): - f_stat, f_dyn, g_load = self._computeLGForces() + f_stat, f_dyn, f_sb, g_load = self._computeLGForces() if self.loadCaseType.lower() == "braking": - nCondition = 2 + if self.weightCondition.lower() == "mlw": - fVert = numpy.zeros(nCondition) - fVert[0] = 0.6 * f_stat - fVert[1] = 0.6 * f_stat + fVert = numpy.zeros(self.nCondition) + fVert[0] = 1.2 * f_stat + fVert[1] = 1.2 * f_stat + + fDrag = numpy.zeros(self.nCondition) + fDrag[0] = 0.96 * f_stat + fDrag[1] = -0.66 * f_stat - fDrag = numpy.zeros(nCondition) - fDrag[0] = 0.48 * f_stat - fDrag[1] = -0.33 * f_stat + fSide = numpy.zeros(self.nCondition) - fSide = numpy.zeros(nCondition) + closure = numpy.zeros(self.nCondition) + closure[0] = 0.75 * self.shockDef + closure[1] = 0.75 * self.shockDef + + gload = numpy.zeros(self.nCondition) + gload[:] = g_load elif self.weightCondition.lower() == "mtow": - fVert = numpy.zeros(nCondition) - fVert[0] = 0.5 * f_stat - fVert[1] = 0.5 * f_stat + fVert = numpy.zeros(self.nCondition) + fVert[0] = 1.0 * f_stat + fVert[1] = 1.0 * f_stat + + fDrag = numpy.zeros(self.nCondition) + fDrag[0] = 0.8 * f_stat + fDrag[1] = -0.55 * f_stat - fDrag = numpy.zeros(nCondition) - fDrag[0] = 0.4 * f_stat - fDrag[1] = -0.275 * f_stat + fSide = numpy.zeros(self.nCondition) - fSide = numpy.zeros(nCondition) + closure = numpy.zeros(self.nCondition) + closure[0] = 0.75 * self.shockDef + closure[1] = 0.75 * self.shockDef + + gload = numpy.zeros(self.nCondition) + gload[:] = g_load else: print("Unrecognized weightCondition:", self.weightCondition) elif self.loadCaseType.lower() == "landing": - nCondition = 5 - fVert = numpy.zeros(nCondition) + fVert = numpy.zeros(self.nCondition) fVert[0] = f_dyn fVert[1] = 0.75 * f_dyn fVert[2] = 0.75 * f_dyn fVert[3] = 0.5 * f_dyn fVert[4] = 0.5 * f_dyn + fVert[5] = 0.8 * f_sb + fVert[6] = 0.8 * f_sb - fDrag = numpy.zeros(nCondition) + fDrag = numpy.zeros(self.nCondition) fDrag[0] = 0.25 * f_dyn fDrag[1] = 0.4 * f_dyn fDrag[2] = 0.4 * f_dyn fDrag[3] = 0.0 fDrag[4] = 0.0 + fDrag[5] = 0.64 * f_sb + fDrag[6] = -0.64 * f_sb # sign convention here is that negative is inboard facing - fSide = numpy.zeros(nCondition) + fSide = numpy.zeros(self.nCondition) fSide[0] = 0 fSide[1] = -0.25 * f_dyn fSide[2] = 0.25 * f_dyn fSide[3] = -0.4 * f_dyn fSide[4] = 0.3 * f_dyn + fSide[5] = 0.0 + fSide[6] = 0.0 + + closure = numpy.zeros(self.nCondition) + closure[0] = 0.5 * self.shockDef + closure[1] = 0.25 * self.shockDef + closure[2] = 0.25 * self.shockDef + closure[3] = 0.5 * self.shockDef + closure[4] = 0.5 * self.shockDef + closure[5] = 0.15 * self.shockDef + closure[6] = 0.15 * self.shockDef + + gload = numpy.zeros(self.nCondition) + gload[:] = g_load else: print("Unrecognized loadCaseType:", self.loadCaseType) - return fVert, fDrag, fSide + closure = self.shockDef - closure + + return fVert, fDrag, fSide, closure, gload + + def writeLoadData(self, fileName): + """ + write a table based on the weight condition + """ + + f_stat, f_dyn, f_sb, g_load = self._computeLGForces() + + caseType = self.weightCondition.upper() + f = open(fileName, "w") + f.write("\\begin{tabular}{lr}\n") + f.write("\\toprule\n") + f.write("Parameter & Value \\\\\n") + f.write(" \\midrule\n") + f.write(" %s $F_\\text{stat}$ (N) &$ %10.0f$ \\\\\n" % (caseType, f_stat)) + f.write(" %s $F_\\text{dyn}$ (N) &$ %10.0f$\\\\\n" % (caseType, f_dyn)) + f.write(" %s Load Factor & %6.3f\\\\\n" % (caseType, g_load)) + f.write("\\bottomrule\n") + f.write("\\end{tabular}\n") + + f.close() + + +# create a dump loads table function diff --git a/baseclasses/pyTransi_problem.py b/baseclasses/pyTransi_problem.py index 53391df..a51406b 100644 --- a/baseclasses/pyTransi_problem.py +++ b/baseclasses/pyTransi_problem.py @@ -1,5 +1,6 @@ """ pyAero_problem + """ from .utils import Error From 7d2260011f56a0b035621afa1a4bacc9704e70a6 Mon Sep 17 00:00:00 2001 From: joanibal Date: Mon, 18 Jan 2021 17:04:41 -0500 Subject: [PATCH 16/21] merge final --- baseclasses/pyAero_geometry.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/baseclasses/pyAero_geometry.py b/baseclasses/pyAero_geometry.py index b1799d7..3e6752a 100644 --- a/baseclasses/pyAero_geometry.py +++ b/baseclasses/pyAero_geometry.py @@ -4,7 +4,6 @@ Holds the Python Aerodynamic Analysis Classes (base and inherited). """ - # ============================================================================= # Geometry Class # ============================================================================= @@ -29,7 +28,7 @@ def __init__( yRootec=0.0, zRootec=0.0, *args, - **kwargs + **kwargs, ): """ @@ -41,9 +40,6 @@ def __init__( Attributes: ----------- - - - Documentation last updated: May. 21, 2008 - Ruben E. Perez """ # @@ -64,8 +60,6 @@ def ListAttributes(self): """ Print Structured Attributes List - - Documentation last updated: May. 21, 2008 - Ruben E. Perez """ ListAttributes(self) @@ -74,8 +68,6 @@ def __str__(self): """ Print Structured List of Variable - - Documentation last updated: May. 21, 2008 - Ruben E. Perez """ return "name \n" + " " + str(self.name).center(9) @@ -88,8 +80,6 @@ def ListAttributes(self): """ Print Structured Attributes List - - Documentation last updated: March. 24, 2008 - Ruben E. Perez """ print("\n") From 2d34c74ac98c4baf4d13f3c24692d445f7fe4b07 Mon Sep 17 00:00:00 2001 From: joanibal Date: Thu, 11 Feb 2021 12:26:22 -0500 Subject: [PATCH 17/21] changed var --- baseclasses/pyAero_problem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baseclasses/pyAero_problem.py b/baseclasses/pyAero_problem.py index af5c1e8..c32c78b 100644 --- a/baseclasses/pyAero_problem.py +++ b/baseclasses/pyAero_problem.py @@ -482,7 +482,7 @@ def setBCVar(self, varName, value, groupName): self.bc_data[groupName][varName] = value - def getBCData(self): + def getBCVars(self): return self.bc_data # def setBCArray(self, groupName, varName, dataArray, patch=None): @@ -499,7 +499,7 @@ def setActuatorVar(self, varName, value, groupName): self.actuatorData[groupName][varName] = value - def getActuatorData(self): + def getActuatorVars(self): return self.actuatorData # def addBCDV(self, bc_var, value=None, lower=None, upper=None, scale=1.0, From fdb1c0b20c8dedb3a70314ab89a541cfdebbce46 Mon Sep 17 00:00:00 2001 From: joanibal Date: Thu, 11 Feb 2021 14:42:01 -0500 Subject: [PATCH 18/21] import err --- baseclasses/pyAero_problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseclasses/pyAero_problem.py b/baseclasses/pyAero_problem.py index c32c78b..5475ddd 100644 --- a/baseclasses/pyAero_problem.py +++ b/baseclasses/pyAero_problem.py @@ -10,7 +10,7 @@ from .ICAOAtmosphere import ICAOAtmosphere from .FluidProperties import FluidProperties from .utils import CaseInsensitiveDict, Error - +from collections import defaultdict class AeroProblem(FluidProperties): """ From f816d289f0479091fe53fdd1a91f7be3ec604196 Mon Sep 17 00:00:00 2001 From: josh Date: Mon, 16 Aug 2021 15:34:34 -0400 Subject: [PATCH 19/21] tweak err msg --- baseclasses/pyAero_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseclasses/pyAero_solver.py b/baseclasses/pyAero_solver.py index b30c1e0..83c135e 100644 --- a/baseclasses/pyAero_solver.py +++ b/baseclasses/pyAero_solver.py @@ -316,7 +316,7 @@ def addFamilyGroup(self, groupName, groups): indices = [] for group in groups: if group.lower() not in self.familyGroups: - raise Error("The specified family '%s' for group '%s', does " + raise Error("The specified familyGroup '%s' for group '%s', does " "not exist in the cgns file or has " "not already been added. The current list of " "families is: %s and the current list of family" From b4f8d86adaac2a48d07929bb4aae6a026567c1c4 Mon Sep 17 00:00:00 2001 From: Josh Anibal Date: Wed, 26 Jan 2022 11:25:51 -0500 Subject: [PATCH 20/21] testing update & act dvs update --- baseclasses/BaseRegTest.py | 11 ++++++----- baseclasses/pyAero_problem.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/baseclasses/BaseRegTest.py b/baseclasses/BaseRegTest.py index 69e940e..cf980d1 100644 --- a/baseclasses/BaseRegTest.py +++ b/baseclasses/BaseRegTest.py @@ -336,16 +336,17 @@ def _add_dict(self, dict_name, d, full_name, db=None, **kwargs): db[dict_name] = {} elif dict_name not in db.keys(): raise ValueError(f"The key '{dict_name}' was not found in the reference file!") - + for key in sorted(d.keys()): - full_name = f"{full_name}: {key}" + full_name_key = f"{full_name}: {key}" + if isinstance(d[key], bool): - self._add_values(key, int(d[key]), rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name) + self._add_values(key, int(d[key]), rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name_key) elif isinstance(d[key], dict): # do some good ol' fashion recursion - self._add_dict(key, d[key], full_name, rtol=rtol, atol=atol, db=db[dict_name]) + self._add_dict(key, d[key], full_name_key, rtol=rtol, atol=atol, db=db[dict_name]) else: - self._add_values(key, d[key], rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name) + self._add_values(key, d[key], rtol=rtol, atol=atol, db=db[dict_name], full_name=full_name_key) # ============================================================================= # reference files I/O diff --git a/baseclasses/pyAero_problem.py b/baseclasses/pyAero_problem.py index 758a659..228fb9d 100644 --- a/baseclasses/pyAero_problem.py +++ b/baseclasses/pyAero_problem.py @@ -353,7 +353,7 @@ def __init__(self, name, **kwargs): ] self.possibleBCDVs = set(BCVarFuncs) - actuatorFuncs = ["Thrust", "Torque"] + actuatorFuncs = ["Thrust", "Torque", "Heat"] self.possibleActuatorDVs = set(actuatorFuncs) # Now determine the possible functions. Any possible design From f826325a5a4c1db879e67a65eb009ea45ba75fbb Mon Sep 17 00:00:00 2001 From: Josh Anibal Date: Thu, 19 May 2022 10:26:36 -0400 Subject: [PATCH 21/21] print args --- baseclasses/problems/pyAero_problem.py | 2 +- baseclasses/utils/__init__.py | 3 ++- baseclasses/utils/utils.py | 28 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/baseclasses/problems/pyAero_problem.py b/baseclasses/problems/pyAero_problem.py index 69f59e9..e93e758 100644 --- a/baseclasses/problems/pyAero_problem.py +++ b/baseclasses/problems/pyAero_problem.py @@ -9,7 +9,7 @@ import warnings from .ICAOAtmosphere import ICAOAtmosphere from .FluidProperties import FluidProperties -from .utils import CaseInsensitiveDict, Error +from ..utils import CaseInsensitiveDict, Error from collections import defaultdict class AeroProblem(FluidProperties): diff --git a/baseclasses/utils/__init__.py b/baseclasses/utils/__init__.py index b2bd75e..fd81351 100644 --- a/baseclasses/utils/__init__.py +++ b/baseclasses/utils/__init__.py @@ -1,6 +1,6 @@ from .containers import CaseInsensitiveSet, CaseInsensitiveDict from .error import Error -from .utils import getPy3SafeString, pp +from .utils import getPy3SafeString, pp, printArgs from .fileIO import writeJSON, readJSON, writePickle, readPickle, redirectIO, redirectingIO __all__ = [ @@ -15,4 +15,5 @@ "readPickle", "redirectIO", "redirectingIO", + "printArgs", ] diff --git a/baseclasses/utils/utils.py b/baseclasses/utils/utils.py index 2506521..f1c9df9 100644 --- a/baseclasses/utils/utils.py +++ b/baseclasses/utils/utils.py @@ -41,3 +41,31 @@ def pp(obj, comm=None, flush=True): # we use pformat to get the string and then call print manually, that way we can flush if we need to pprint_str = pformat(obj) print(pprint_str, flush=flush) + +def printArgs(args): + """ + Prints the arguments passed to the script. + + Parameters + ---------- + args : argparse.Namespace + The object holding the arguments passed to the script from + args = parser.parse_args() + """ + args_dict = vars(args) + longest_key = max(args_dict.keys(), key=len) + longest_val = max([str(x) for x in args_dict.values()], key=len) + divider = " : " + + box_width = len(longest_key) + len(longest_val) + len(divider) + + bar = "-" * box_width + + # add title in the middle of the bar to create header + title = " Arguments " + header = bar[:(len(bar) - len(title))//2] + title + bar[(len(bar)+len(title))//2:] + + print(header) + for arg, arg_val in args_dict.items(): + print(f"{arg:{len(longest_key)}}" + divider + f"{arg_val}") + print(bar)