Source code for pygeo.mphys.mphys_dvgeo

# External modules
from mpi4py import MPI
import numpy as np
import openmdao.api as om
from openmdao.api import AnalysisError

# Local modules
from .. import DVConstraints, DVGeometry, DVGeometryESP, DVGeometryMulti, DVGeometryVSP


# class that actually calls the DVGeometry methods
[docs] class OM_DVGEOCOMP(om.ExplicitComponent):
[docs] def initialize(self): r""" Set up a geometry component with either 1 DVGeo or multiple DVGeos. A single DVGeo are initialized by specifying its file and type and, optionally, additional options. Available options can be found in the specific DVGeometry class. Multiple DVGeos are initialized in a dictionary of these values and must have a unique name. The format is: >>> DVGeoInfo = { >>> "name1": {"file": file1, "type": type1, "options": options1} >>> "name2": {"file": file2, "type": type2, "options": options2} >>> } The two setup methods cannot currently be used together. """ self.options.declare("file", default=None) self.options.declare("type", default=None) self.options.declare("options", default=None) self.options.declare("DVGeoInfo", default=None)
def setup(self): # create a constraints object to go with this DVGeo(s) self.DVCon = DVConstraints() # hold the DVGeo(s) in a dictionary self.DVGeos = {} # conventional setup with one DVGeo. maintains old interface if self.options["DVGeoInfo"] is None: self.multDVGeo = False # set up DVGeoInfo so a single DVGeo can be initialized with the multi-DVGeo case DVGeoInfo = { "defaultDVGeo": { "file": self.options["file"], "type": self.options["type"], "options": self.options["options"], } } # DVGeo and DVCon expect different defaults DVConName = "default" DVGeoName = None # we need to add multiple DVGeos to this geometry component # the actual initialization is handled in the same way regardless else: self.multDVGeo = True DVGeoInfo = self.options["DVGeoInfo"] # create the DVGeo object that does the computations (or multiple DVGeo objects) for name, info in DVGeoInfo.items(): if self.multDVGeo: DVGeoName = DVConName = name if info.get("options") is None: options = {} else: options = info["options"] # this DVGeo uses FFD if info["type"] == "ffd": self.DVGeos.update({name: DVGeometry(info["file"], name=DVGeoName, **options)}) # this DVGeo uses VSP elif info["type"] == "vsp": self.DVGeos.update({name: DVGeometryVSP(info["file"], comm=self.comm, name=DVGeoName, **options)}) # this DVGeo uses ESP elif info["type"] == "esp": self.DVGeos.update({name: DVGeometryESP(info["file"], comm=self.comm, name=DVGeoName, **options)}) elif info["type"] == "multi": self.DVGeos.update({name: DVGeometryMulti(comm=self.comm, **options)}) # add each geometry to the constraints object for _, DVGeo in self.DVGeos.items(): self.DVCon.setDVGeo(DVGeo, name=DVConName) self.omPtSetList = [] def compute(self, inputs, outputs): # check for inputs that have been added but the points have not been added to dvgeo for var in inputs.keys(): # check that the input name matches the convention for points if var[:2] == "x_": # trim the _in and add a "0" to signify that these are initial conditions initial var_out = var[:-3] + "0" if var_out not in self.omPtSetList: self.nom_addPointSet(inputs[var], var_out, add_output=False) # handle DV update and pointset changes for all of our DVGeos for _, DVGeo in self.DVGeos.items(): # inputs are the geometric design variables DVGeo.setDesignVars(inputs) # ouputs are the coordinates of the pointsets we have for ptName in DVGeo.points: if ptName in self.omPtSetList: # update this pointset and write it as output outputs[ptName] = DVGeo.update(ptName).flatten() # compute the DVCon constraint values constraintfunc = dict() self.DVCon.evalFunctions(constraintfunc, includeLinear=True) for constraintname in constraintfunc: # if any constraint returned a fail flag throw an error to OpenMDAO # all constraints need the same fail flag, no <name_> prefix if constraintname == "fail": raise AnalysisError("Analysis error in geometric constraints") outputs[constraintname] = constraintfunc[constraintname] # we ran a compute so the inputs changed. update the dvcon jac # next time the jacvec product routine is called self.update_jac = True
[docs] def nom_addComponent(self, comp, ffd_file, triMesh, DVGeoName=None): """ Add a component a DVGeometryMulti object. This is a wrapper for the DVGeoMulti.addComponent method. Parameters ---------- comp : str The name of the component. ffd_file : str Path to the FFD file for the new DVGeo object for this component. triMesh : str, optional Path to the triangulated mesh file for this component. DVGeoName : str, optional The name of the DVGeo object to add this component to. """ # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # can only add a DVGeo to a DVGeoMulti if not isinstance(DVGeo, DVGeometryMulti): raise RuntimeError( f"Only multi-based DVGeo objects can have components added to them, not type:{self.geo_type}" ) # Add component DVGeoComp = DVGeometry(ffd_file) DVGeo.addComponent(comp=comp, DVGeo=DVGeoComp, triMesh=triMesh)
def nom_addChild(self, ffd_file, DVGeoName=None, childName=None, comp=None): # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # can only add a child to a FFD DVGeo if not isinstance(DVGeo, DVGeometry): raise RuntimeError( f"Only FFD-based DVGeo objects can have children added to them, not type: {type(DVGeo).__name__}" ) # Add child FFD child_ffd = DVGeometry(ffd_file, child=True, name=childName) if comp is None: DVGeo.addChild(child_ffd) else: DVGeo.DVGeoDict[comp].addChild(child_ffd) # Embed points from parent if not already done for pointSet in DVGeo.points: if pointSet not in child_ffd.points: child_ffd.addPointSet(DVGeo.points[pointSet], pointSet) def nom_add_discipline_coords(self, discipline, points=None, DVGeoName=None, **kwargs): # TODO remove one of these methods to keep only one method to add pointsets if points is None: # no pointset info is provided, just do a generic i/o. We will add these points during the first compute self.add_input("x_%s_in" % discipline, distributed=True, shape_by_conn=True) self.add_output("x_%s0" % discipline, distributed=True, copy_shape="x_%s_in" % discipline) else: # we are provided with points. we can do the full initialization now self.nom_addPointSet(points, "x_%s0" % discipline, add_output=False, DVGeoName=DVGeoName, **kwargs) self.add_input("x_%s_in" % discipline, distributed=True, val=points.flatten()) self.add_output("x_%s0" % discipline, distributed=True, val=points.flatten()) def nom_addPointSet(self, points, ptName, add_output=True, DVGeoName=None, **kwargs): # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # add the points to the dvgeo object dMaxGlobal = None if isinstance(DVGeo, DVGeometryESP): # DVGeoESP can return a value to check the pointset distribution dMaxGlobal = DVGeo.addPointSet(points.reshape(len(points) // 3, 3), ptName, **kwargs) else: DVGeo.addPointSet(points.reshape(len(points) // 3, 3), ptName, **kwargs) self.omPtSetList.append(ptName) if isinstance(DVGeo, DVGeometry): for child in DVGeo.children.values(): # Embed points from parent if not already done for pointSet in DVGeo.points: if pointSet not in child.points: child.addPointSet(DVGeo.points[pointSet], pointSet) if add_output: # add an output to the om component self.add_output(ptName, distributed=True, val=points.flatten()) return dMaxGlobal def nom_add_point_dict(self, point_dict): # add every pointset in the dict, and set the ptset name as the key for k, v in point_dict.items(): self.nom_addPointSet(v, k)
[docs] def nom_getDVGeo(self, childName=None, comp=None, DVGeoName=None): """ Gets the DVGeometry object held in the geometry component so DVGeo methods can be called directly on it Parameters ---------- childName : str, optional Name of the child FFD, if you want a child DVGeo returned comp : str, optional Name of the DVGeoMulti component, if this DV is for a multi component DVGeoName : str, optional The name of the DVGeo to return, necessary if there are multiple DVGeo objects Returns ------- DVGeometry object DVGeometry object held by this geometry component """ # if we have multiple DVGeos use the one specified by name if self.multDVGeo: DVGeo = self.DVGeos[DVGeoName] else: DVGeo = self.DVGeos["defaultDVGeo"] # return a child of a DVGeoMulti component if childName is not None and comp is not None: return DVGeo.DVGeoDict[comp].children[childName] # return a component of a DVGeoMulti elif comp is not None: return DVGeo.DVGeoDict[comp] # return a child of a DVGeo elif childName is not None: return DVGeo.children[childName] # return the top level DVGeo else: return DVGeo
[docs] def nom_getDVCon(self): """ Gets the DVConstraints object held in the geometry component so DVCon methods can be called directly on it Returns ------- self.DVCon, DVConstraints object DVConstraints object held by this geometry component """ return self.DVCon
""" Wrapper for DVGeo functions """
[docs] def nom_addGlobalDV( self, dvName, value, func, childName=None, comp=None, isComposite=False, DVGeoName=None, prependName=False, config=None, ): """ Add a global design variable to the DVGeo object. This is a wrapper for the DVGeo.addGlobalDV method. Parameters ---------- dvName : str See :meth:`addGlobalDV <.DVGeometry.addGlobalDV>` value : float, or iterable list of floats See :meth:`addGlobalDV <.DVGeometry.addGlobalDV>` func : python function See :meth:`addGlobalDV <.DVGeometry.addGlobalDV>` childName : str, optional Name of the child FFD, if this DV is for a child FFD. comp : str, optional Name of the DVGeoMulti component, if this DV is for a multi component isComposite : bool, optional Whether this DV is to be included in the composite DVs, by default False DVGeoName : string, optional The name of the DVGeo to return, necessary if there are multiple DVGeo objects Raises ------ RuntimeError Raised if the underlying DVGeo object is not an FFD """ # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(childName=childName, comp=comp, DVGeoName=DVGeoName) # global DVs are only added to FFD-based DVGeo objects if not isinstance(DVGeo, (DVGeometry, DVGeometryMulti)): raise RuntimeError(f"Only FFD-based DVGeo objects can use global DVs, not type: {type(DVGeo).__name__}") # if this DVGeo object has a name attribute, prepend it to match up with what DVGeo is expecting # this keeps track of DVs between multiple DVGeo objects if DVGeoName is not None and prependName: dvName = DVGeoName + "_" + dvName # call the dvgeo object and add this dv DVGeo.addGlobalDV(dvName, value, func, prependName=False, config=config) # define the input # When composite DVs are used, input is not required for the default DVs. Now the composite DVs are # the actual DVs. So OpenMDAO don't need the default DVs as inputs. if not isComposite: self.add_input(dvName, distributed=False, shape=len(np.atleast_1d(value)))
def nom_addLocalDV( self, dvName, axis="y", pointSelect=None, childName=None, comp=None, isComposite=False, DVGeoName=None, prependName=False, volList=None, config=None, ): # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(childName=childName, comp=comp, DVGeoName=DVGeoName) # local DVs are only added to FFD-based DVGeo objects if not isinstance(DVGeo, (DVGeometry, DVGeometryMulti)): raise RuntimeError(f"Only FFD-based DVGeo objects can use local DVs, not type: {type(DVGeo).__name__}") # if this DVGeo object has a name attribute, prepend it to match up with what DVGeo is expecting # this keeps track of DVs between multiple DVGeo objects if DVGeoName is not None and prependName: dvName = DVGeoName + "_" + dvName # add the DV to DVGeo nVal = DVGeo.addLocalDV( dvName, axis=axis, pointSelect=pointSelect, prependName=False, config=config, volList=volList ) # define the input # When composite DVs are used, input is not required for the default DVs. Now the composite DVs are # the actual DVs. So OpenMDAO don't need the default DVs as inputs. if not isComposite: self.add_input(dvName, distributed=False, shape=nVal) return nVal
[docs] def nom_addLocalSectionDV( self, dvName, secIndex, childName=None, comp=None, axis=1, pointSelect=None, volList=None, orient0=None, orient2="svd", config=None, DVGeoName=None, prependName=False, ): """ Add one or more section local design variables to the DVGeometry object Wrapper for :meth:`addLocalSectionDV <.DVGeometry.addLocalSectionDV>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- dvName : str Name to give this design variable secIndex : char or list of chars See wrapped childName : str, optional Name of the child FFD, if this DV is for a child FFD. comp : str, optional Name of the DVGeoMulti component, if this DV is for a multi component axis : int, optional See wrapped pointSelect : pointSelect object, optional See wrapped volList : list, optional See wrapped orient0 : orientation, optional See wrapped orient2 : str, optional See wrapped config : str or list, optional See wrapped DVGeoName : string, optional The name of the DVGeo to return, necessary if there are multiple DVGeo objects Returns ------- nVal, int number of local section DVs Raises ------ RuntimeError Raised if the underlying DVGeo parameterization is not FFD-based """ # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(childName=childName, DVGeoName=DVGeoName) # local DVs are only added to FFD-based DVGeo objects if not isinstance(DVGeo, (DVGeometry, DVGeometryMulti)): raise RuntimeError( f"Only FFD-based DVGeo objects can use local section DVs, not type: {type(DVGeo).__name__}" ) # if this DVGeo object has a name attribute, prepend it to match up with what DVGeo is expecting # this keeps track of DVs between multiple DVGeo objects if DVGeoName is not None and prependName: dvName = DVGeoName + "_" + dvName # add the DV to DVGeo nVal = DVGeo.addLocalSectionDV( dvName, secIndex, axis, pointSelect, volList, orient0, orient2, config, prependName=False ) # define the input self.add_input(dvName, distributed=False, shape=nVal) return nVal
[docs] def nom_addShapeFunctionDV( self, dvName, shapes, childName=None, comp=None, config=None, DVGeoName=None, prependName=False ): """ Add one or more local shape function design variables to the DVGeometry object Wrapper for :meth:`addShapeFunctionDV <.DVGeometry.addShapeFunctionDV>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- dvName : str Name to give this design variable shapes : list of dictionaries, or a single dictionary See wrapped childName : str, optional Name of the child FFD, if this DV is for a child FFD. comp : str, optional Name of the DVGeoMulti component, if this DV is for a multi component config : str or list, optional See wrapped DVGeoName : string, optional The name of the DVGeo to return, necessary if there are multiple DVGeo objects Returns ------- N : int The number of design variables added. Raises ------ RuntimeError Raised if the underlying DVGeo parameterization is not FFD-based """ # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(childName=childName, DVGeoName=DVGeoName) # shape function DVs are only added to FFD-based DVGeo objects if not isinstance(DVGeo, DVGeometry): raise RuntimeError( f"Only FFD-based DVGeo objects can use shape function DVs, not type: {type(DVGeo).__name__}" ) # if this DVGeo object has a name attribute, prepend it to match up with what DVGeo is expecting # this keeps track of DVs between multiple DVGeo objects if DVGeoName is not None and prependName: dvName = DVGeoName + "_" + dvName # add the DV to DVGeo nVal = DVGeo.addShapeFunctionDV(dvName, shapes, config, prependName=False) # define the input self.add_input(dvName, distributed=False, shape=nVal) return nVal
def nom_addGeoCompositeDV( self, dvName, ptSetName=None, u=None, scale=None, DVGeoName=None, prependName=False, **kwargs ): # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # if this DVGeo object has a name attribute, prepend it to match up with what DVGeo is expecting # this keeps track of DVs between multiple DVGeo objects if DVGeoName is not None and prependName: dvName = DVGeoName + "_" + dvName # call the dvgeo object and add this dv DVGeo.addCompositeDV(dvName, ptSetName=ptSetName, u=u, scale=scale, prependName=False, **kwargs) val = DVGeo.getValues() # define the input self.add_input(dvName, distributed=False, shape=DVGeo.getNDV(), val=val[dvName][0]) def nom_addVSPVariable(self, component, group, parm, isComposite=False, DVGeoName=None, **kwargs): # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # VSP DVs are only added to VSP-based DVGeo objects if not isinstance(DVGeo, DVGeometryVSP): raise RuntimeError(f"Only VSP-based DVGeo objects can use VSP DVs, not type: {type(DVGeo).__name__}") # actually add the DV to VSP DVGeo.addVariable(component, group, parm, **kwargs) # full name of this DV dvName = "%s:%s:%s" % (component, group, parm) # get the value val = DVGeo.DVs[dvName].value.copy() # define the input # When composite DVs are used, input is not required for the default DVs. Now the composite DVs are # the actual DVs. So OpenMDAO don't need the default DVs as inputs. if not isComposite: self.add_input(dvName, distributed=False, shape=1, val=val)
[docs] def nom_addESPVariable(self, desmptr_name, rows=None, cols=None, dh=0.001, isComposite=False, DVGeoName=None): """ Add an ESP design variables to the DVGeometryESP object Wrapper for :meth:`addVariable <.DVGeometryESP.addVariable>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- desmptr_name : str See :meth:`addVariable <.DVGeometryESP.addVariable>` rows : list or None, optional See :meth:`addVariable <.DVGeometryESP.addVariable>` cols : list or None, optional See :meth:`addVariable <.DVGeometryESP.addVariable>` dh : float, optional See :meth:`addVariable <.DVGeometryESP.addVariable>` isComposite : bool, optional Whether this DV is to be included in the composite DVs, by default False DVGeoName : string, optional The name of the DVGeo to add DVs to, necessary if there are multiple DVGeo objects Raises ------ RuntimeError Raised if the underlying DVGeo parameterization is not ESP-based """ # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(DVGeoName=DVGeoName) # ESP DVs are only added to VSP-based DVGeo objects if not isinstance(DVGeo, DVGeometryESP): raise RuntimeError(f"Only ESP-based DVGeo objects can use ESP DVs, not type: {type(DVGeo).__name__}") # actually add the DV to ESP DVGeo.addVariable(desmptr_name, rows=rows, cols=cols, dh=dh) # get the value val = DVGeo.DVs[desmptr_name].value.copy() # add the input with the correct value, VSP DVs always have a size of 1 # When composite DVs are used, input is not required for the default DVs. Now the composite DVs are # the actual DVs. So OpenMDAO don't need the default DVs as inputs. if not isComposite: self.add_input(desmptr_name, distributed=False, shape=val.shape, val=val)
def nom_addRefAxis( self, name, childName=None, comp=None, DVGeoName=None, curve=None, xFraction=None, yFraction=None, zFraction=None, volumes=None, rotType=5, axis="x", alignIndex=None, rotAxisVar=None, rot0ang=None, rot0axis=[1, 0, 0], includeVols=[], ignoreInd=[], raySize=1.5, ): # TODO: we should change `volume` to `volList`, to be consistent with other APIs. # But doing this may create backward incompatibility. So we will use `volumes` for now # if we have multiple DVGeos use the one specified by name DVGeo = self.nom_getDVGeo(childName=childName, comp=comp, DVGeoName=DVGeoName) # references axes are only needed in FFD-based DVGeo objects if not isinstance(DVGeo, (DVGeometry, DVGeometryMulti)): raise RuntimeError(f"Only FFD-based DVGeo objects can use reference axes, not type: {type(DVGeo).__name__}") # add ref axis to this DVGeo return DVGeo.addRefAxis( name=name, curve=curve, xFraction=xFraction, yFraction=yFraction, zFraction=zFraction, volumes=volumes, rotType=rotType, axis=axis, alignIndex=alignIndex, rotAxisVar=rotAxisVar, rot0ang=rot0ang, rot0axis=rot0axis, includeVols=includeVols, ignoreInd=ignoreInd, raySize=raySize, ) # add ref axis to the specified child """ Wrapper for DVCon functions """ def nom_addThicknessConstraints2D( self, name, leList, teList, nSpan, nChord, scaled=True, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None, projected=False, ): self.DVCon.addThicknessConstraints2D( leList, teList, nSpan, nChord, name=name, scaled=scaled, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, projected=projected, ) self.add_output(name, distributed=False, val=np.ones((nSpan * nChord,)), shape=nSpan * nChord) def nom_addThicknessConstraints1D( self, name, ptList, nCon, axis, scaled=True, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None, projected=False, ): self.DVCon.addThicknessConstraints1D( ptList, nCon, axis, name=name, scaled=scaled, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, projected=projected, ) self.add_output(name, distributed=False, val=np.ones(nCon), shape=nCon)
[docs] def nom_addVolumeConstraint( self, name, leList, teList, nSpan=10, nChord=10, scaled=True, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None, ): """ Add a DVCon volume constraint to the problem Wrapper for :meth:`addVolumeConstraint <.DVConstraints.addVolumeConstraint>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- name : See wrapped leList : See wrapped teList : See wrapped nSpan : int, optional See wrapped nChord : int, optional See wrapped scaled : bool, optional See wrapped surfaceName : str, optional See wrapped DVGeoName : str, optional See wrapped compNames : list, optional See wrapped """ self.DVCon.addVolumeConstraint( leList, teList, nSpan=nSpan, nChord=nChord, scaled=scaled, name=name, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, ) self.add_output(name, distributed=False, val=1.0)
[docs] def nom_addSurfaceAreaConstraint( self, name, scaled=True, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None ): """ Add a DVCon surface area constraint to the problem Wrapper for :meth:`addSurfaceAreaConstraint <.DVConstraints.addSurfaceAreaConstraint>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- name : See wrapped scaled : bool, optional See wrapped surfaceName : str, optional See wrapped DVGeoName : str, optional See wrapped compNames : list, optional See wrapped """ self.DVCon.addSurfaceAreaConstraint( name=name, scaled=scaled, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, ) self.add_output(name, distributed=False, val=1.0)
[docs] def nom_addProjectedAreaConstraint( self, name, axis, scaled=True, addToPyOpt=True, surface_name="default", DVGeoName="default", compNames=None ): """ Add a DVCon projected area constraint to the problem Wrapper for :meth:`addProjectedAreaConstraint <.DVConstraints.addProjectedAreaConstraint>` Input parameters are identical to those in wrapped function unless otherwise specified Parameters ---------- name : See wrapped axis : See wrapped scaled : bool, optional See wrapped surface_name : str, optional See wrapped DVGeoName : str, optional See wrapped compNames : list, optional See wrapped """ self.DVCon.addProjectedAreaConstraint( axis, name=name, scaled=scaled, addToPyOpt=addToPyOpt, surfaceName=surface_name, DVGeoName=DVGeoName, compNames=compNames, ) self.add_output(name, distributed=False, val=1.0)
def nom_add_LETEConstraint( self, name, volID, faceID, topID=None, indSetA=None, indSetB=None, config=None, childName=None, comp=None, DVGeoName="default", ): self.DVCon.addLeTeConstraints( volID=volID, faceID=faceID, topID=topID, indSetA=indSetA, indSetB=indSetB, name=name, config=config, childName=childName, comp=comp, DVGeoName=DVGeoName, ) # how many are there? conobj = self.DVCon.linearCon[name] nCon = len(conobj.indSetA) self.add_output(name, distributed=False, val=np.zeros((nCon,)), shape=nCon) return nCon def nom_addLERadiusConstraints( self, name, leList, nSpan, axis, chordDir, scaled=True, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None, ): self.DVCon.addLERadiusConstraints( leList=leList, nSpan=nSpan, axis=axis, chordDir=chordDir, name=name, scaled=scaled, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, ) self.add_output(name, distributed=False, val=np.ones(nSpan), shape=nSpan) def nom_addCurvatureConstraint1D( self, name, start, end, nPts, axis, curvatureType="mean", scaled=True, KSCoeff=1.0, addToPyOpt=True, surfaceName="default", DVGeoName="default", compNames=None, ): self.DVCon.addCurvatureConstraint1D( start=start, end=end, nPts=nPts, axis=axis, name=name, curvatureType=curvatureType, scaled=scaled, KSCoeff=KSCoeff, addToPyOpt=addToPyOpt, surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, ) self.add_output(name, distributed=False, val=1.0) def nom_addLinearConstraintsShape( self, name, indSetA, indSetB, factorA, factorB, config=None, childName=None, comp=None, DVGeoName="default" ): self.DVCon.addLinearConstraintsShape( indSetA=indSetA, indSetB=indSetB, factorA=factorA, factorB=factorB, name=name, config=config, childName=childName, comp=comp, DVGeoName=DVGeoName, ) lSize = len(indSetA) self.add_output(name, distributed=False, val=np.zeros(lSize), shape=lSize) def nom_addTriangulatedSurfaceConstraint( self, name, surface_1_name=None, DVGeo_1_name="default", surface_2_name="default", DVGeo_2_name="default", rho=50.0, heuristic_dist=None, max_perim=3.0, addToPyOpt=True, ): self.DVCon.addTriangulatedSurfaceConstraint( comm=self.comm, surface_1_name=surface_1_name, DVGeo_1_name=DVGeo_1_name, surface_2_name=surface_2_name, DVGeo_2_name=DVGeo_2_name, rho=rho, heuristic_dist=heuristic_dist, max_perim=max_perim, name=name, addToPyOpt=addToPyOpt, ) self.add_output(f"{name}_KS", distributed=False, val=0) self.add_output(f"{name}_perim", distributed=False, val=0) def nom_setConstraintSurface( self, surface, name="default", addToDVGeo=False, DVGeoName="default", surfFormat="point-vector" ): # constraint needs a triangulated reference surface at initialization self.DVCon.setSurface(surface, name=name, addToDVGeo=addToDVGeo, DVGeoName=DVGeoName, surfFormat=surfFormat) def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): # only do the computations when we have more than zero entries in d_inputs # in the reverse mode or d_outputs in the forward mode doRev = mode == "rev" and len(list(d_inputs.keys())) > 0 doFwd = mode == "fwd" and len(list(d_outputs.keys())) > 0 if doFwd or doRev: # this flag will be set to True after every compute call. # if it is true, we assume the design has changed so we re-run the sensitivity update # there can be hundreds of calls to this routine due to thickness constraints, # as a result, we only run the actual sensitivity comp once and save the jacobians # this might be better suited with the matrix-based API if self.update_jac: self.constraintfuncsens = dict() self.DVCon.evalFunctionsSens(self.constraintfuncsens, includeLinear=True) # set the flag to False so we dont run the update again if this is called w/o a compute in between self.update_jac = False # Directly do Jacobian vector product with the derivatives from DVConstraints for constraintname in self.constraintfuncsens: for dvname in self.constraintfuncsens[constraintname]: if constraintname in d_outputs and dvname in d_inputs: dcdx = self.constraintfuncsens[constraintname][dvname] if doFwd: din = d_inputs[dvname] jvtmp = np.dot(dcdx, din) d_outputs[constraintname] += jvtmp elif doRev: dout = d_outputs[constraintname] jvtmp = np.dot(np.transpose(dcdx), dout) d_inputs[dvname] += jvtmp for _, DVGeo in self.DVGeos.items(): dvs = DVGeo.getVarNames() # Collet point sets from all DVGeos if DVGeometryMulti object if isinstance(DVGeo, DVGeometryMulti): ptSetNames = [] for comp in DVGeo.DVGeoDict.keys(): for ptSet in DVGeo.DVGeoDict[comp].ptSetNames: if ptSet not in ptSetNames: ptSetNames.append(ptSet) else: ptSetNames = DVGeo.ptSetNames for ptSetName in ptSetNames: if ptSetName in self.omPtSetList: # Process the seeds if doFwd: # Collect the d_inputs associated with the current DVGeo seeds = {} for k in d_inputs: if k in dvs: seeds[k] = d_inputs[k] elif doRev: seeds = d_outputs[ptSetName].reshape(len(d_outputs[ptSetName]) // 3, 3) # only do the calc. if seeds are not zero on ANY proc local_all_zeros = np.all(seeds == 0) global_all_zeros = np.zeros(1, dtype=bool) # we need to communicate for this check otherwise we may hang self.comm.Allreduce([local_all_zeros, MPI.BOOL], [global_all_zeros, MPI.BOOL], MPI.LAND) # Compute the Jacobian vector product if not global_all_zeros[0]: if doFwd: d_outputs[ptSetName] += DVGeo.totalSensitivityProd(seeds, ptSetName) elif doRev: # TODO totalSensitivityTransProd is broken. does not work with zero surface nodes on a proc # xdot = DVGeo.totalSensitivityTransProd(dout, ptSetName) xdot = DVGeo.totalSensitivity(seeds, ptSetName) # loop over dvs and accumulate xdotg = {} for k in xdot: # check if this dv is present if k in d_inputs: # do the allreduce xdotg[k] = self.comm.allreduce(xdot[k], op=MPI.SUM) # accumulate in the dict d_inputs[k] += xdotg[k][0]