Esempio n. 1
0
class OM_DVGEOCOMP(om.ExplicitComponent):
    def initialize(self):

        self.options.declare("ffd_file", default=None)
        self.options.declare("vsp_file", default=None)
        self.options.declare("vsp_options", default=None)

    def setup(self):

        # create the DVGeo object that does the computations
        if self.options["ffd_file"] is not None:
            # we are doing an FFD-based DVGeo
            ffd_file = self.options["ffd_file"]
            self.DVGeo = DVGeometry(ffd_file)
        if self.options["vsp_file"] is not None:
            # we are doing a VSP based DVGeo
            vsp_file = self.options["vsp_file"]
            vsp_options = self.options["vsp_options"]
            self.DVGeo = DVGeometryVSP(vsp_file, comm=self.comm, **vsp_options)

        self.DVCon = DVConstraints()
        self.DVCon.setDVGeo(self.DVGeo)
        self.omPtSetList = []

    def compute(self, inputs, outputs):
        # check for inputs thathave 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)

        # inputs are the geometric design variables
        self.DVGeo.setDesignVars(inputs)

        # ouputs are the coordinates of the pointsets we have
        for ptName in self.DVGeo.points:
            if ptName in self.omPtSetList:
                # update this pointset and write it as output
                outputs[ptName] = self.DVGeo.update(ptName).flatten()

        # compute the DVCon constraint values
        constraintfunc = dict()
        self.DVCon.evalFunctions(constraintfunc, includeLinear=True)
        comm = self.comm
        if comm.rank == 0:
            for constraintname in constraintfunc:
                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

    def nom_add_discipline_coords(self, discipline, points=None):
        # 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)
            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, **kwargs):
        # add the points to the dvgeo object
        self.DVGeo.addPointSet(points.reshape(len(points) // 3, 3), ptName,
                               **kwargs)
        self.omPtSetList.append(ptName)

        if add_output:
            # add an output to the om component
            self.add_output(ptName, distributed=True, val=points.flatten())

    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)

    def nom_addGeoDVGlobal(self, dvName, value, func):
        # define the input
        self.add_input(dvName, distributed=False, shape=len(value))

        # call the dvgeo object and add this dv
        self.DVGeo.addGeoDVGlobal(dvName, value, func)

    def nom_addGeoDVLocal(self, dvName, axis="y", pointSelect=None):
        nVal = self.DVGeo.addGeoDVLocal(dvName,
                                        axis=axis,
                                        pointSelect=pointSelect)
        self.add_input(dvName, distributed=False, shape=nVal)
        return nVal

    def nom_addVSPVariable(self, component, group, parm, **kwargs):

        # actually add the DV to VSP
        self.DVGeo.addVariable(component, group, parm, **kwargs)

        # full name of this DV
        dvName = "%s:%s:%s" % (component, group, parm)

        # get the value
        val = self.DVGeo.DVs[dvName].value.copy()

        # add the input with the correct value, VSP DVs always have a size of 1
        self.add_input(dvName, distributed=False, shape=1, val=val)

    def nom_addThicknessConstraints2D(self,
                                      name,
                                      leList,
                                      teList,
                                      nSpan=10,
                                      nChord=10):
        self.DVCon.addThicknessConstraints2D(leList,
                                             teList,
                                             nSpan,
                                             nChord,
                                             lower=1.0,
                                             name=name)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name,
                            distributed=True,
                            val=np.ones((nSpan * nChord, )),
                            shape=nSpan * nChord)
        else:
            self.add_output(name, distributed=True, shape=(0, ))

    def nom_addThicknessConstraints1D(self, name, ptList, nCon, axis):
        self.DVCon.addThicknessConstraints1D(ptList, nCon, axis, name=name)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name,
                            distributed=True,
                            val=np.ones(nCon),
                            shape=nCon)
        else:
            self.add_output(name, distributed=True, shape=(0))

    def nom_addVolumeConstraint(self,
                                name,
                                leList,
                                teList,
                                nSpan=10,
                                nChord=10):
        self.DVCon.addVolumeConstraint(leList,
                                       teList,
                                       nSpan=nSpan,
                                       nChord=nChord,
                                       name=name)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name, distributed=True, val=1.0)
        else:
            self.add_output(name, distributed=True, shape=0)

    def nom_add_LETEConstraint(self, name, volID, faceID, topID=None):
        self.DVCon.addLeTeConstraints(volID, faceID, name=name, topID=topID)
        # how many are there?
        conobj = self.DVCon.linearCon[name]
        nCon = len(conobj.indSetA)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name,
                            distributed=True,
                            val=np.zeros((nCon, )),
                            shape=nCon)
        else:
            self.add_output(name, distributed=True, shape=0)
        return nCon

    def nom_addLERadiusConstraints(self, name, leList, nSpan, axis, chordDir):
        self.DVCon.addLERadiusConstraints(leList=leList,
                                          nSpan=nSpan,
                                          axis=axis,
                                          chordDir=chordDir,
                                          name=name)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name,
                            distributed=True,
                            val=np.ones(nSpan),
                            shape=nSpan)
        else:
            self.add_output(name, distributed=True, shape=0)

    def nom_addCurvatureConstraint1D(self, name, start, end, nPts, axis,
                                     **kwargs):
        self.DVCon.addCurvatureConstraint1D(start=start,
                                            end=end,
                                            nPts=nPts,
                                            axis=axis,
                                            name=name,
                                            **kwargs)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name, distributed=True, val=1.0)
        else:
            self.add_output(name, distributed=True, shape=0)

    def nom_addLinearConstraintsShape(self, name, indSetA, indSetB, factorA,
                                      factorB):
        self.DVCon.addLinearConstraintsShape(indSetA=indSetA,
                                             indSetB=indSetB,
                                             factorA=factorA,
                                             factorB=factorB,
                                             name=name)
        lSize = len(indSetA)
        comm = self.comm
        if comm.rank == 0:
            self.add_output(name,
                            distributed=True,
                            val=np.zeros(lSize),
                            shape=lSize)
        else:
            self.add_output(name, distributed=True, shape=0)

    def nom_addRefAxis(self, **kwargs):
        # we just pass this through
        return self.DVGeo.addRefAxis(**kwargs)

    def nom_setConstraintSurface(self, surface):
        # constraint needs a triangulated reference surface at initialization
        self.DVCon.setSurface(surface)

    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
        ni = len(list(d_inputs.keys()))

        if mode == "rev" and ni > 0:

            # 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

            for constraintname in self.constraintfuncsens:
                for dvname in self.constraintfuncsens[constraintname]:
                    if dvname in d_inputs:
                        dcdx = self.constraintfuncsens[constraintname][dvname]
                        if self.comm.rank == 0:
                            dout = d_outputs[constraintname]
                            jvtmp = np.dot(np.transpose(dcdx), dout)
                        else:
                            jvtmp = 0.0
                        d_inputs[dvname] += jvtmp
                        # OM does the reduction itself
                        # d_inputs[dvname] += self.comm.reduce(jvtmp, op=MPI.SUM, root=0)

            for ptSetName in self.DVGeo.ptSetNames:
                if ptSetName in self.omPtSetList:
                    dout = d_outputs[ptSetName].reshape(
                        len(d_outputs[ptSetName]) // 3, 3)

                    # only do the calc. if d_output is not zero on ANY proc
                    local_all_zeros = np.all(dout == 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)

                    # global_all_zeros is a numpy array of size 1
                    if not global_all_zeros[0]:

                        # TODO totalSensitivityTransProd is broken. does not work with zero surface nodes on a proc
                        # xdot = self.DVGeo.totalSensitivityTransProd(dout, ptSetName)
                        xdot = self.DVGeo.totalSensitivity(dout, 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
                                # TODO reove the allreduce when this is fixed in openmdao
                                # reduce the result ourselves for now. ideally, openmdao will do the reduction itself when this is fixed. this is because the bcast is also done by openmdao (pyoptsparse, but regardless, it is not done here, so reduce should also not be done here)
                                xdotg[k] = self.comm.allreduce(xdot[k],
                                                               op=MPI.SUM)

                                # accumulate in the dict
                                # TODO
                                # because we only do one point set at a time, we always want the 0th
                                # entry of this array since dvgeo always behaves like we are passing
                                # in multiple objective seeds with totalSensitivity. we can remove the [0]
                                # once we move back to totalSensitivityTransProd
                                d_inputs[k] += xdotg[k][0]
Esempio n. 2
0
class PlateComponentLHS(om.ExplicitComponent):
    """Robust Bump Flow Problem, with LHS Monte Carlo Samples"""
    def initialize(self):
        # Need to modify this dictionary when we change the SA constants
        #import pdb; pdb.set_trace()
        #sys.stdout = open(os.devnull, "w")
        self.aoptions = aeroOptions
        self.woptions = warpOptions
        self.ooptions = optOptions
        self.uoptions = uqOptions

        self.Pr = 0.
        self.P = self.uoptions['P']
        self.NS0 = self.uoptions['NS0']
        # Generate FFD and DVs
        if rank == 0:
            rank0dvg = pf.createFFD()
        else:
            rank0dvg = None
        self.DVGeo = comm.bcast(rank0dvg, root=0)

        # starting flat mesh
        meshname = self.aoptions['gridFile']
        gridFile = meshname

        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']  #0.95
        Re = self.ooptions['Re']  #50000
        Re_L = 1.0
        temp = 540
        arearef = 2.0
        chordref = 1.0

        # Spalart Allmaras model constants, to be changed in UQ (4 for now)
        saconstsm = [0.41, 0.1355, 0.622, 0.66666666667]
        self.saconstsb = [7.1, 0.3, 2.0, 1.0, 2.0, 1.2, 0.5, 2.0]
        self.saconsts = saconstsm + self.saconstsb
        self.aoptions['SAConsts'] = self.saconsts
        #self.gridSol = f'{meshname}_{saconstsm}_sol'
        solname = self.ooptions['prob_name']
        self.gridSol = f'{solname}_sol'

        # Get a set of UQ sample points (LHS)
        #if self.ooptions['run_once']:
        #    self.sample = self.uoptions['dist']
        #else

        # Scatter samples, multi-point parallelism
        if self.uoptions['MCTimeBudget']:
            self.aps = []
            self.solvers = []
            self.meshes = []
            self.current_samples = self.NS0
            if rank == 0:
                rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
            else:
                rank0sam = None
            self.sample = comm.bcast(rank0sam, root=0)
            self.cases = divide_cases(self.NS0, size)
            # Scatter samples on each level, multi-point parallelism
            self.samplep = self.sample[self.cases[rank]]
            self.nsp = len(self.cases[rank])

            # Create solvers for the preliminary data
            for i in range(self.nsp):
                namestr = self.gridSol + "_" + str(self.cases[rank][i])

                # create meshes
                self.meshes.append(
                    USMesh(options=self.woptions, comm=MPI.COMM_SELF))

                # create aeroproblems
                self.aps.append(
                    AeroProblem(name=namestr,
                                alpha=alpha,
                                mach=mach,
                                reynolds=Re,
                                reynoldsLength=Re_L,
                                T=temp,
                                areaRef=arearef,
                                chordRef=chordref,
                                evalFuncs=['cd']))
                time.sleep(0.1)  # this solves a few problems for some reason
                # create solvers
                self.solvers.append(
                    ADFLOW(options=self.aoptions, comm=MPI.COMM_SELF))

                saconstsm = self.samplep[i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[i].setOption('SAConsts', self.saconsts)
                self.solvers[i].setDVGeo(self.DVGeo)
                self.solvers[i].setMesh(self.meshes[i])
                print("what up %i", str(rank))
                coords = self.solvers[i].getSurfaceCoordinates(
                    groupName=self.solvers[i].allWallsGroup)
                self.solvers[i].DVGeo.addPointSet(coords, 'coords')

            # start looping over mesh levels
            sumt = 0.
            sumtp = 0.
            Et = 0.
            funcs = {}
            a_init = self.DVGeo.getValues()
            a_init['pnts'][:] = self.ooptions['DVInit']
            dvdict = {'pnts': a_init['pnts']}
            for i in range(self.nsp):
                saconstsm = self.samplep[i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[i].setOption('SAConsts', self.saconsts)
                self.solvers[i].DVGeo.setDesignVars(dvdict)
                self.aps[i].setDesignVars(dvdict)
                pc0 = time.process_time()
                self.solvers[i](self.aps[i])
                self.solvers[i].evalFunctions(self.aps[i], funcs)
                pc1 = time.process_time()
                astr = self.gridSol + "_" + str(self.cases[rank][i]) + "_cd"
                sumtp += (pc1 - pc0)

            sumt = comm.allreduce(sumtp)
            Et = sumt / self.NS0
            self.NS = math.ceil(self.P / Et)
            self.Pr = self.NS * Et
        else:
            self.NS = self.uoptions['NS']

        #import pdb; pdb.set_trace()

        if rank == 0:
            rank0sam = plate_sa_lhs.genLHS(s=self.NS)
        else:
            rank0sam = None
        self.sample = comm.bcast(rank0sam, root=0)

        self.cases = divide_cases(self.NS, size)
        self.nsp = len(self.cases[rank])  #int(ns/size) # samples per processor
        #import pdb; pdb.set_trace()
        self.samplep = self.sample[self.cases[
            rank]]  #self.sample[(rank*self.nsp):(rank*self.nsp+(self.nsp))] #shouldn't really need to "scatter" per se
        #import pdb; pdb.set_trace()
        #assert len(self.samplep) == self.nsp

        # Actually create solvers (and aeroproblems?) (and mesh?) now
        self.aps = []
        self.solvers = []
        self.meshes = []

        #self.mesh = USMesh(options=self.woptions, comm=MPI.COMM_SELF)
        for i in range(self.nsp):
            namestr = self.gridSol + "_" + str(self.cases[rank][i])

            # create meshes
            self.meshes.append(
                USMesh(options=self.woptions, comm=MPI.COMM_SELF))

            # create aeroproblems
            self.aps.append(
                AeroProblem(name=namestr,
                            alpha=alpha,
                            mach=mach,
                            reynolds=Re,
                            reynoldsLength=Re_L,
                            T=temp,
                            areaRef=arearef,
                            chordRef=chordref,
                            evalFuncs=['cd']))
            time.sleep(0.1)  # this solves a few problems for some reason
            # create solvers
            self.solvers.append(
                ADFLOW(options=self.aoptions, comm=MPI.COMM_SELF))
            # if not self.ooptions['run_once']:
            # saconstsm = self.samplep[i].tolist()
            # else:
            saconstsm = self.samplep[i].tolist()
            self.saconsts = saconstsm + self.saconstsb
            self.solvers[i].setOption('SAConsts', self.saconsts)
            self.solvers[i].setDVGeo(self.DVGeo)
            self.solvers[i].setMesh(self.meshes[i])
            print("what up %i", str(rank))
            coords = self.solvers[i].getSurfaceCoordinates(
                groupName=self.solvers[i].allWallsGroup)
            self.solvers[i].DVGeo.addPointSet(coords, 'coords')

        # Set constraints, should only need one of those solvers, the meshes are all the same
        self.DVCon = DVConstraints()
        self.DVCon2 = DVConstraints()
        self.DVCon.setDVGeo(self.solvers[0].DVGeo.getFlattenedChildren()[1])
        self.DVCon2.setDVGeo(self.solvers[0].DVGeo)

        self.DVCon.setSurface(self.solvers[0].getTriangulatedMeshSurface(
            groupName='allSurfaces'))
        # set extra group for surface area condition
        self.DVCon2.setSurface(self.solvers[0].getTriangulatedMeshSurface(),
                               name='wall')

        # DV should be same into page (not doing anything right now)
        #import pdb; pdb.set_trace()
        lIndex = self.solvers[0].DVGeo.getFlattenedChildren()[1].getLocalIndex(
            0)
        indSetA = []
        indSetB = []
        nXc = optOptions['NX']
        self.NC = math.trunc(
            ((1.0 - self.ooptions['DVFraction']) * self.ooptions['NX']))
        ind = [
            int(nXc / 2) - int(self.NC / 2),
            int(nXc / 2) + int(self.NC / 2)
        ]
        for i in range(ind[0], ind[1]):
            indSetA.append(lIndex[i, 0, 1])
            indSetB.append(lIndex[i, 1, 1])
        # for i in range(lIndex.shape[0]):
        #     indSetA.append(lIndex[i, 0, 1])
        #     indSetB.append(lIndex[i, 1, 1])
        self.DVCon.addLinearConstraintsShape(indSetA,
                                             indSetB,
                                             factorA=1.0,
                                             factorB=-1.0,
                                             lower=0,
                                             upper=0,
                                             name='eqs')

        # Thickness constraints (one for each active DV)
        #import pdb; pdb.set_trace()

        # Maximum thickness of the domain, translates to minimum thickness of bump
        ub = 1.0 - self.ooptions['DCMinThick']
        tcf = self.ooptions['DCThickFrac']
        ra = self.ooptions['bumpBounds']
        lim = self.ooptions['DCMinArea']
        span = numpy.linspace(0, 1, nXc)
        xc = span * (ra[1] - ra[0]) + ra[0]
        #ind = range(int(nXc/2) - int(self.NC/2), int(nXc/2) + int(self.NC/2)))
        ind = [
            int(nXc / 2) - int(tcf * self.NC / 2),
            int(nXc / 2) + int(tcf * self.NC / 2)
        ]
        ptList = numpy.zeros([2, 3])
        ptList[:, 0] = xc[ind]
        ptList[:, 1] = 0.5
        ptList[:, 2] = 0.5

        if self.ooptions['use_area_con']:
            self.DVCon2.addSurfaceAreaConstraint(lower=lim,
                                                 upper=10.,
                                                 name='sas',
                                                 surfaceName='wall')
        else:
            self.DVCon2.addThicknessConstraints1D(ptList,
                                                  self.NC, [0, 0, 1],
                                                  lower=0.5,
                                                  upper=ub,
                                                  name='tcs')

        print("excuse me")
        dummy = rank
        dsum = comm.allgather(dummy)

        sys.stdout = sys.__stdout__

    def setup(self):
        #initialize shape and set deformation points as inputs
        a_init = self.solvers[0].DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']
        # mult = numpy.linspace(1.0,1.5,num=int(0.5*len(a_init['pnts'])))
        # mult = numpy.concatenate((mult, mult))
        #if self.ooptions['run_once']:
        #    a_init['pnts'] = self.ooptions['ro_shape']
        #a_init['pnts'] = numpy.multiply(mult, a_init['pnts'])
        self.add_input('a', a_init['pnts'], desc="Bump Shape Control Points")
        #self.add_input('a', 0.2, desc="Bump Shape Control Points")

        if self.ooptions['use_area_con']:
            self.add_output('SA', 1.0, desc='Surface Area Constraint')
        else:
            self.add_output('TC',
                            numpy.zeros(self.NC),
                            desc='Thickness Constraints')
        self.add_output('Cd_m', 0.0, desc="Mean Drag Coefficient")
        self.add_output('Cd_v', 0.0, desc="Variance Drag Coefficient")
        self.add_output('Cd_r', 0.0, desc="Robust Drag Objective")
        self.add_output('EQ',
                        numpy.zeros(int(len(a_init['pnts']) / 2)),
                        desc="Control Point Symmetry")
        self.add_output('N1', self.NS, desc="Number of samples used")
        self.add_output('Pr', self.Pr, desc="MFMC Samples at each level")

    def setup_partials(self):
        self.declare_partials('Cd_m', 'a', method='exact')
        self.declare_partials('Cd_v', 'a', method='exact')
        self.declare_partials('Cd_r', 'a', method='exact')
        if self.ooptions['use_area_con']:
            self.declare_partials('SA', 'a', method='exact')
        else:
            self.declare_partials('TC', 'a', method='exact')
        self.declare_partials('EQ', 'a', method='exact')

    def compute(self, inputs, outputs):
        # run the bump shape model
        #import pdb; pdb.set_trace()
        # evaluate each sample point
        #print("hello")
        dvdict = {'pnts': inputs['a']}
        funcs = {}
        ns = self.NS
        sump = 0.
        musp = numpy.zeros(self.nsp)
        #self.aps[0].setDesignVars(dvdict)
        for i in range(self.nsp):
            self.solvers[i].DVGeo.setDesignVars(dvdict)
            self.aps[i].setDesignVars(dvdict)
            self.solvers[i](self.aps[i])
            self.solvers[i].evalFunctions(self.aps[i], funcs)
            astr = self.gridSol + "_" + str(self.cases[rank][i]) + "_cd"
            #import pdb; pdb.set_trace()
            musp[i] = funcs[astr]
            sump += funcs[astr]

        # compute mean and variance
        sum = comm.allreduce(sump)
        mus = comm.allgather(musp)
        #import pdb; pdb.set_trace()
        E = sum / ns
        sum2 = 0.
        for i in range(len(mus)):  #range(size):
            for j in range(len(mus[i])):  #range(self.nsp):
                sum2 += (mus[i][j] - E)**2
        V = sum2 / ns

        self.DVCon.evalFunctions(funcs, includeLinear=True)
        self.DVCon2.evalFunctions(funcs, includeLinear=True)

        outputs['Cd_m'] = E
        outputs['Cd_v'] = math.sqrt(V)
        rho = self.uoptions['rho']
        outputs['Cd_r'] = E + rho * math.sqrt(V)
        if self.ooptions['use_area_con']:
            outputs['SA'] = funcs['sas']
        else:
            outputs['TC'] = funcs['tcs']

        outputs['EQ'] = funcs['eqs']
        outputs['N1'] = self.NS
        outputs['Pr'] = self.Pr
        #outputs['Cd'] = inputs['a']*inputs['a']

    def compute_partials(self, inputs, J):

        dvdict = {'pnts': inputs['a']}
        funcs = {}
        funcSens = {}
        ns = self.NS
        sump = 0.
        musp = numpy.zeros(self.nsp)
        pmup = []
        psump = numpy.zeros(len(inputs['a']))
        self.aps[0].setDesignVars(dvdict)
        #import pdb; pdb.set_trace()
        for i in range(self.nsp):
            self.solvers[i].DVGeo.setDesignVars(dvdict)
            self.aps[i].setDesignVars(dvdict)
            self.solvers[i].evalFunctions(self.aps[i], funcs)
            self.solvers[i].evalFunctionsSens(self.aps[i], funcSens, ['cd'])
            astr = self.gridSol + "_" + str(self.cases[rank][i]) + "_cd"
            arr = [x * (1. / ns) for x in funcSens[astr]['pnts']]
            sump += funcs[astr]
            musp[i] = funcs[astr]
            pmup.append(arr[0])
            psump += arr[0]

        sum = comm.allreduce(sump)
        mus = comm.allgather(musp)
        pmu = comm.allgather(pmup)
        psum = comm.allreduce(psump)
        #import pdb; pdb.set_trace()
        #import pdb; pdb.set_trace()

        # compute variance sensitivity
        E = sum / ns
        sum2 = 0.
        psum2 = numpy.zeros(len(inputs['a']))
        for i in range(len(mus)):  #range(size):
            for j in range(len(mus[i])):  #range(self.nsp):
                sum2 += (mus[i][j] - E)**2

                temp = pmu[i][j] * ns - psum
                arr2 = [x * 2 * (mus[i][j] - E) / ns for x in temp]
                psum2 += arr2
                #import pdb; pdb.set_trace()
        V = sum2 / ns
        #import pdb; pdb.set_trace()

        self.DVCon.evalFunctionsSens(funcSens, includeLinear=True)
        self.DVCon2.evalFunctionsSens(funcSens, includeLinear=True)

        J['Cd_m', 'a'] = psum
        J['Cd_v', 'a'] = (1. / (2 * math.sqrt(V))) * psum2
        rho = self.uoptions['rho']
        J['Cd_r', 'a'] = psum + rho * (1. / (2 * math.sqrt(V))) * psum2
        if self.ooptions['use_area_con']:
            J['SA', 'a'] = funcSens['sas']['pnts']
        else:
            J['TC', 'a'] = funcSens['tcs']['pnts']
        J['EQ', 'a'] = funcSens['eqs']['pnts']
Esempio n. 3
0
class PlateComponentMLMC(om.ExplicitComponent):
    """Robust Bump Flow Problem, with LHS Multi Level Monte Carlo Samples"""
    def initialize(self):
        # Need to modify this dictionary when we change the SA constants
        #import pdb; pdb.set_trace()
        sys.stdout = open(os.devnull, "w")
        self.aoptions = aeroOptions
        self.woptions = warpOptions
        self.ooptions = optOptions
        self.uoptions = uqOptions

        # Generate FFD and DVs
        if rank == 0:
            rank0dvg = pf.createFFD()
        else:
            rank0dvg = None
        self.DVGeo = comm.bcast(rank0dvg, root=0)

        # Get a full list of every mesh name we have, assume they are ordered properly by level
        self.meshnames = self.uoptions['gridFileLevels']
        self.Lmax = len(self.meshnames)  #as many levels as we have meshes

        if self.Lmax < 3:
            raise ValueError('Not enough meshes available, ' + self.Lmax +
                             ' < 3')

        # Spalart Allmaras model constants, to be changed in UQ (4 for now)
        saconstsm = [0.41, 0.1355, 0.622, 0.66666666667]
        self.saconstsb = [7.1, 0.3, 2.0, 1.0, 2.0, 1.2, 0.5, 2.0]
        self.saconsts = saconstsm + self.saconstsb
        self.aoptions['SAConsts'] = self.saconsts
        #self.gridSol = f'{meshname}_{saconstsm}_sol'
        solname = self.ooptions['prob_name']
        self.gridSol = f'{solname}_sol'

        self.cases = []
        self.nsp = []  #keep track per level
        self.samplep = []
        self.naddedtot = []
        for i in range(self.Lmax):
            self.naddedtot.append(0)

        self.aps = []
        self.solvers = []
        self.meshes = []

        self.NS0 = self.uoptions['NS0']
        self.current_samples = self.NS0 * self.Lmax
        self.N1 = None
        ##########
        # call distribute samples here?
        if not self.uoptions['use-predetermined-samples']:
            self.MLMC()
        else:
            self.N1 = self.uoptions['predet-N1']

        self.dist_samples()

        ##########

        # Set constraints, should only need one of those solvers, the meshes are all the same
        if rank == 0:
            self.DVCon = DVConstraints()
            self.DVCon2 = DVConstraints()
            self.DVCon.setDVGeo(
                self.solvers[self.Lmax - 1][0].DVGeo.getFlattenedChildren()[1])
            self.DVCon2.setDVGeo(self.solvers[self.Lmax - 1][0].DVGeo)

            self.DVCon.setSurface(
                self.solvers[self.Lmax - 1][0].getTriangulatedMeshSurface(
                    groupName='allSurfaces'))
            # set extra group for surface area condition
            self.DVCon2.setSurface(
                self.solvers[self.Lmax - 1][0].getTriangulatedMeshSurface(),
                name='wall')

            # DV should be same into page (not doing anything right now)
            #import pdb; pdb.set_trace()
            lIndex = self.solvers[
                self.Lmax -
                1][0].DVGeo.getFlattenedChildren()[1].getLocalIndex(0)
            indSetA = []
            indSetB = []
            nXc = optOptions['NX']
            self.NC = math.trunc(
                ((1.0 - self.ooptions['DVFraction']) * self.ooptions['NX']))
            ind = [
                int(nXc / 2) - int(self.NC / 2),
                int(nXc / 2) + int(self.NC / 2)
            ]
            for i in range(ind[0], ind[1]):
                indSetA.append(lIndex[i, 0, 1])
                indSetB.append(lIndex[i, 1, 1])
            # for i in range(lIndex.shape[0]):
            #     indSetA.append(lIndex[i, 0, 1])
            #     indSetB.append(lIndex[i, 1, 1])
            self.DVCon.addLinearConstraintsShape(indSetA,
                                                 indSetB,
                                                 factorA=1.0,
                                                 factorB=-1.0,
                                                 lower=0,
                                                 upper=0,
                                                 name='eqs')

            # Thickness constraints (one for each active DV)
            #import pdb; pdb.set_trace()

            # Maximum thickness of the domain, translates to minimum thickness of bump
            ub = 1.0 - self.ooptions['DCMinThick']
            tcf = self.ooptions['DCThickFrac']
            ra = self.ooptions['bumpBounds']
            lim = self.ooptions['DCMinArea']
            span = numpy.linspace(0, 1, nXc)
            xc = span * (ra[1] - ra[0]) + ra[0]
            #ind = range(int(nXc/2) - int(self.NC/2), int(nXc/2) + int(self.NC/2)))
            ind = [
                int(nXc / 2) - int(tcf * self.NC / 2),
                int(nXc / 2) + int(tcf * self.NC / 2)
            ]
            ptList = numpy.zeros([2, 3])
            ptList[:, 0] = xc[ind]
            ptList[:, 1] = 0.5
            ptList[:, 2] = 0.5

            if self.ooptions['use_area_con']:
                self.DVCon2.addSurfaceAreaConstraint(lower=lim,
                                                     upper=10.,
                                                     name='sas',
                                                     surfaceName='wall')
            else:
                self.DVCon2.addThicknessConstraints1D(ptList,
                                                      self.NC, [0, 0, 1],
                                                      lower=0.5,
                                                      upper=ub,
                                                      name='tcs')

        dummy = rank
        dsum = comm.allgather(dummy)

        sys.stdout = sys.__stdout__

    def setup(self):
        #initialize shape and set deformation points as inputs
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']
        # mult = numpy.linspace(1.0,1.5,num=int(0.5*len(a_init['pnts'])))
        # mult = numpy.concatenate((mult, mult))
        #if self.ooptions['run_once']:
        #    a_init['pnts'] = self.ooptions['ro_shape']
        #a_init['pnts'] = numpy.multiply(mult, a_init['pnts'])
        self.add_input('a', a_init['pnts'], desc="Bump Shape Control Points")
        #self.add_input('a', 0.2, desc="Bump Shape Control Points")

        if self.ooptions['use_area_con']:
            self.add_output('SA', 1.0, desc='Surface Area Constraint')
        else:
            self.add_output('TC',
                            numpy.zeros(self.NC),
                            desc='Thickness Constraints')
        self.add_output('Cd_m', 0.0, desc="Mean Drag Coefficient")
        self.add_output('Cd_v', 0.0, desc="Variance Drag Coefficient")
        self.add_output('Cd_r', 0.0, desc="Robust Drag Objective")
        self.add_output('EQ',
                        numpy.zeros(int(len(a_init['pnts']) / 2)),
                        desc="Control Point Symmetry")

        self.add_output('N1', self.N1, desc="MLMC Samples at each level")

    def setup_partials(self):
        self.declare_partials('Cd_m', 'a', method='exact')
        self.declare_partials('Cd_v', 'a', method='exact')
        self.declare_partials('Cd_r', 'a', method='exact')
        if self.ooptions['use_area_con']:
            self.declare_partials('SA', 'a', method='exact')
        else:
            self.declare_partials('TC', 'a', method='exact')
        self.declare_partials('EQ', 'a', method='exact')

    def compute(self, inputs, outputs):
        # run the bump shape model
        #import pdb; pdb.set_trace()

        dvdict = {'pnts': inputs['a']}
        ntot = self.current_samples
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
        funcs = {}
        sump = []
        musp = []
        sum1 = []
        mus = []
        sumpm = []
        muspm = []
        summ = []
        musm = []
        E = numpy.zeros(self.Lmax)
        V = numpy.zeros(self.Lmax)
        for k in range(self.Lmax):
            sump.append(0.0)
            musp.append(numpy.zeros(nslp[k]))
            sumpm.append(0.)
            muspm.append(numpy.zeros(nslp[k]))
            for i in range(nslp[k]):
                saconstsm = self.samplep[k][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[k][i].setOption('SAConsts', self.saconsts)
                self.solvers[k][i].DVGeo.setDesignVars(dvdict)
                self.aps[k][i].setDesignVars(dvdict)
                self.solvers[k][i](self.aps[k][i])
                self.solvers[k][i].evalFunctions(self.aps[k][i], funcs)
                astr = self.gridSol + "_" + str(self.cases[k][rank][i]) + "_cd"
                musp[k][i] = funcs[astr]
                sump[k] += funcs[astr]
                if k > 0:
                    self.solvers[k][i + nslp[k]].setOption(
                        'SAConsts', self.saconsts)
                    self.solvers[k][i + nslp[k]].DVGeo.setDesignVars(dvdict)
                    self.aps[k][i + nslp[k]].setDesignVars(dvdict)
                    self.solvers[k][i + nslp[k]](self.aps[k][i + nslp[k]])
                    self.solvers[k][i + nslp[k]].evalFunctions(
                        self.aps[k][i + nslp[k]], funcs)
                    astr = self.gridSol + "_" + str(
                        self.cases[k][rank][i]) + "_m_cd"
                    muspm[k][i] = -funcs[astr]
                    sumpm[k] += -funcs[astr]

        # compute mean and variance
        for k in range(self.Lmax):
            sum1.append(comm.allreduce(sump[k]))
            mus.append(comm.allgather(musp[k]))
            summ.append(comm.allreduce(sumpm[k]))
            musm.append(comm.allgather(muspm[k]))
            #import pdb; pdb.set_trace()
            E[k] = (sum1[k] + summ[k]) / nslt[k]
            sum2 = 0.
            for i in range(len(mus[k])):  #loop over processors
                for j in range(len(
                        mus[k][i])):  #loop over samples on processors
                    if k > 0:
                        sum2 += ((mus[k][i][j] + musm[k][i][j]) - E[k])**2
                    else:
                        sum2 += (mus[k][i][j] - E[k])**2
            V[k] = sum2 / nslt[k]

        import pdb
        pdb.set_trace()
        if rank == 0:
            self.DVCon.evalFunctions(funcs, includeLinear=True)
            self.DVCon2.evalFunctions(funcs, includeLinear=True)
            eq0 = funcs['eqs']
            sa0 = funcs['sas']
        else:
            eq0 = None
            sa0 = None
        eq = comm.bcast(eq0, root=0)
        sa = comm.bcast(sa0, root=0)

        outputs['Cd_m'] = sum(E)
        outputs['Cd_v'] = V[0]  #by assumption
        rho = self.uoptions['rho']
        outputs['Cd_r'] = sum(E) + rho * math.sqrt(V[0])
        if self.ooptions['use_area_con']:
            outputs['SA'] = sa
        else:
            outputs['TC'] = funcs['tcs']

        outputs['EQ'] = eq
        outputs['N1'] = self.N1
        #outputs['Cd'] = inputs['a']*inputs['a']

    def compute_partials(self, inputs, J):

        dvdict = {'pnts': inputs['a']}
        funcs = {}
        funcSens = {}
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
        sump = []
        musp = []
        sum1 = []
        sum2 = []
        mus = []

        sumpm = []
        muspm = []
        summ = []
        musm = []

        pmup = []
        psump = []  #numpy.zeros(len(inputs['a']))
        pmu = []
        psum1 = []

        pmupm = []
        psumpm = []  #numpy.zeros(len(inputs['a']))
        pmum = []
        psumm = []

        E = numpy.zeros(self.Lmax)
        V = numpy.zeros(self.Lmax)
        #import pdb; pdb.set_trace()
        for k in range(self.Lmax):
            sump.append(0.0)
            musp.append(numpy.zeros(nslp[k]))
            sumpm.append(0.)
            muspm.append(numpy.zeros(nslp[k]))
            pmup.append([])
            psump.append(numpy.zeros(len(inputs['a'])))
            pmupm.append([])
            psumpm.append(numpy.zeros(len(inputs['a'])))
            for i in range(nslp[k]):
                saconstsm = self.samplep[k][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[k][i].setOption('SAConsts', self.saconsts)
                self.solvers[k][i].DVGeo.setDesignVars(dvdict)
                self.aps[k][i].setDesignVars(dvdict)
                self.solvers[k][i](self.aps[k][i])
                self.solvers[k][i].evalFunctions(self.aps[k][i], funcs)
                self.solvers[k][i].evalFunctionsSens(self.aps[k][i], funcSens,
                                                     ['cd'])
                astr = self.gridSol + "_" + str(self.cases[k][rank][i]) + "_cd"
                arr = [x * (1. / nslt[k]) for x in funcSens[astr]['pnts']]
                musp[k][i] = funcs[astr]
                sump[k] += funcs[astr]
                pmup[k].append(arr[0])
                psump[k] += arr[0]
                if k > 0:
                    self.solvers[k][i + nslp[k]].setOption(
                        'SAConsts', self.saconsts)
                    self.solvers[k][i + nslp[k]].DVGeo.setDesignVars(dvdict)
                    #import pdb; pdb.set_trace()
                    self.aps[k][i + nslp[k]].setDesignVars(dvdict)
                    self.solvers[k][i + nslp[k]](self.aps[k][i + nslp[k]])
                    self.solvers[k][i + nslp[k]].evalFunctions(
                        self.aps[k][i + nslp[k]], funcs)
                    self.solvers[k][i + nslp[k]].evalFunctionsSens(
                        self.aps[k][i + nslp[k]], funcSens, ['cd'])
                    astr = self.gridSol + "_" + str(
                        self.cases[k][rank][i]) + "_m_cd"
                    arr = [x * (1. / nslt[k]) for x in funcSens[astr]['pnts']]
                    muspm[k][i] = -funcs[astr]
                    sumpm[k] += -funcs[astr]
                    pmupm[k].append(-arr[0])
                    psumpm[k] -= arr[0]

        # compute mean and variance
        psum2 = numpy.zeros(len(inputs['a']))
        for k in range(self.Lmax):
            sum1.append(comm.allreduce(sump[k]))
            mus.append(comm.allgather(musp[k]))
            summ.append(comm.allreduce(sumpm[k]))
            musm.append(comm.allgather(muspm[k]))
            psum1.append(comm.allreduce(psump[k]))
            pmu.append(comm.allgather(pmup[k]))
            psumm.append(comm.allreduce(psumpm[k]))
            pmum.append(comm.allgather(pmupm[k]))

            E[k] = (sum1[k] + summ[k]) / nslt[k]
        sum2 = 0.
        for i in range(len(mus[0])):  #loop over processors
            for j in range(len(mus[0][i])):  #loop over samples on processors
                #import pdb; pdb.set_trace()
                sum2 += (mus[0][i][j] - E[0])**2
                temp = pmu[0][i][j] * nslt[0] - psum1[0]
                arr2 = [x * 2 * (mus[0][i][j] - E[0]) / nslt[0] for x in temp]
                psum2 += arr2
        V = sum2 / nslt[0]

        # compute variance sensitivity

        #import pdb; pdb.set_trace()
        if rank == 0:
            self.DVCon.evalFunctionsSens(funcSens, includeLinear=True)
            self.DVCon2.evalFunctionsSens(funcSens, includeLinear=True)
            eq0p = funcSens['eqs']['pnts']
            sa0p = funcSens['sas']['pnts']
        else:
            eq0p = None
            sa0p = None
        eqp = comm.bcast(eq0p, root=0)
        sap = comm.bcast(sa0p, root=0)

        J['Cd_m', 'a'] = sum(psum1) + sum(psumm)
        J['Cd_v', 'a'] = psum2
        rho = self.uoptions['rho']
        J['Cd_r',
          'a'] = sum(psum1) + sum(psumm) + rho * (1. /
                                                  (2 * math.sqrt(V))) * psum2
        #import pdb; pdb.set_trace()
        if self.ooptions['use_area_con']:
            J['SA', 'a'] = sap
        else:
            J['TC', 'a'] = funcSens['tcs']['pnts']
        J['EQ', 'a'] = eqp

    #J['Cd','a'][0] = 2*inputs['a']

    def MLMC(self):
        # Use an MLMC algorithm to determine an optimal sample distribution between existing mesh levels
        # We do this once before optimization, then compute statistics with the same set of samples at every iteration

        # start with initial samples
        # Get a set of UQ sample points (LHS), enough for each level at the start
        #sys.stdout = open(os.devnull, "w")

        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']  #0.95
        Re = self.ooptions['Re']  #50000
        Re_L = 1.0
        tempR = 540
        arearef = 2.0
        chordref = 1.0
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']

        self.current_samples = self.NS0 * self.Lmax
        if rank == 0:
            rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
        else:
            rank0sam = None
        self.sample = comm.bcast(rank0sam, root=0)

        # Scatter samples on each level, multi-point parallelism

        for i in range(self.Lmax):
            self.cases.append(divide_cases(self.NS0, size))
            for j in range(len(self.cases[i])):
                for k in range(len(self.cases[i][j])):
                    self.cases[i][j][k] += i * self.NS0
            #self.nsp.append(len(self.cases[i][rank]))#int(ns/size) # samples per processor
            self.samplep.append(self.sample[self.cases[i][rank]])
        #import pdb; pdb.set_trace()
        #self.samplep = self.sample[self.cases[rank]]#self.sample[(rank*self.nsp):(rank*self.nsp+(self.nsp))] #shouldn't really need to "scatter" per se
        #import pdb; pdb.set_trace()
        # for i in range(self.Lmax):
        #     assert len(self.samplep[i]) == self.nsp[i]

        # Actually create solvers (and aeroproblems?) (and mesh?) now
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            alist = []
            slist = []
            mlist = []
            alist2 = []
            slist2 = []
            mlist2 = []
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            for i in range(nslp[k]):
                namestr = self.gridSol + "_" + str(self.cases[k][rank][i])

                # create meshes
                leveloptions = self.woptions
                leveloptions['gridFile'] = self.meshnames[k]
                mlist.append(USMesh(options=leveloptions, comm=MPI.COMM_SELF))

                # create aeroproblems
                aloptions = self.aoptions
                aloptions['gridFile'] = self.meshnames[k]
                alist.append(
                    AeroProblem(name=namestr,
                                alpha=alpha,
                                mach=mach,
                                reynolds=Re,
                                reynoldsLength=Re_L,
                                T=tempR,
                                areaRef=arearef,
                                chordRef=chordref,
                                evalFuncs=['cd']))
                time.sleep(0.1)  # this solves a few problems for some reason
                # create solvers
                slist.append(ADFLOW(options=aloptions, comm=MPI.COMM_SELF))

                # if not self.ooptions['run_once']:
                #     saconstsm = self.samplep[i].tolist()
                # else:
                saconstsm = self.samplep[k][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                slist[i].setOption('SAConsts', self.saconsts)
                slist[i].setDVGeo(self.DVGeo)
                slist[i].setMesh(mlist[i])
                coords = slist[i].getSurfaceCoordinates(
                    groupName=slist[i].allWallsGroup)
                slist[i].DVGeo.addPointSet(coords, 'coords')

                if k > 0:  #create additional solvers at higher levels for the estimators
                    # create meshes
                    namestr = self.gridSol + "_" + str(
                        self.cases[k][rank][i]) + "_m"
                    leveloptions = self.woptions
                    leveloptions['gridFile'] = self.meshnames[k - 1]
                    mlist2.append(
                        USMesh(options=leveloptions, comm=MPI.COMM_SELF))
                    # create aeroproblems
                    aloptions = self.aoptions
                    aloptions['gridFile'] = self.meshnames[k - 1]
                    alist2.append(
                        AeroProblem(name=namestr,
                                    alpha=alpha,
                                    mach=mach,
                                    reynolds=Re,
                                    reynoldsLength=Re_L,
                                    T=tempR,
                                    areaRef=arearef,
                                    chordRef=chordref,
                                    evalFuncs=['cd']))
                    time.sleep(
                        0.1)  # this solves a few problems for some reason
                    # create solvers
                    slist2.append(ADFLOW(options=aloptions,
                                         comm=MPI.COMM_SELF))
                    slist2[i].setOption('SAConsts', self.saconsts)
                    slist2[i].setDVGeo(self.DVGeo)
                    slist2[i].setMesh(mlist2[i])
                    coords = slist[i].getSurfaceCoordinates(
                        groupName=slist2[i].allWallsGroup)
                    slist2[i].DVGeo.addPointSet(coords, 'coords')

            self.aps.append(alist)
            self.solvers.append(slist)
            self.meshes.append(mlist)
            if k > 0:
                self.aps[k] += alist2
                self.solvers[k] += slist2
                self.meshes[k] += mlist2
        #import pdb; pdb.set_trace()
        # start looping over mesh levels
        L = 0
        M = 4.0  #0.5 #refinement factor?
        converged = 0
        eps = self.uoptions['vartol']
        sum1 = []
        mus = []
        sump = []
        musp = []
        sumpm = []
        muspm = []
        summ = []
        musm = []
        E = []
        V = []
        N1 = []
        while ~converged & L < self.Lmax:
            # compute start up samples to estimate variance
            dvdict = {'pnts': a_init['pnts']}
            funcs = {}
            nslp = []
            nslt = []
            for k in range(self.Lmax):
                nslp.append(len(self.cases[k][rank]))
                nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            sump.append(0.)
            musp.append(numpy.zeros(nslp[L]))
            sumpm.append(0.)
            muspm.append(numpy.zeros(nslp[L]))

            for i in range(nslp[L]):
                # just do this again in case
                saconstsm = self.samplep[L][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[L][i].setOption('SAConsts', self.saconsts)
                self.solvers[L][i].DVGeo.setDesignVars(dvdict)
                self.aps[L][i].setDesignVars(dvdict)
                self.solvers[L][i](self.aps[L][i])
                self.solvers[L][i].evalFunctions(self.aps[L][i], funcs)
                astr = self.gridSol + "_" + str(self.cases[L][rank][i]) + "_cd"
                musp[L][i] = funcs[astr]
                sump[L] += funcs[astr]
                #import pdb; pdb.set_trace()
                if L > 0:
                    self.solvers[L][i + nslp[L]].setOption(
                        'SAConsts', self.saconsts)
                    self.solvers[L][i + nslp[L]].DVGeo.setDesignVars(dvdict)
                    self.aps[L][i + nslp[L]].setDesignVars(dvdict)
                    self.solvers[L][i + nslp[L]](self.aps[L][i + nslp[L]])
                    self.solvers[L][i + nslp[L]].evalFunctions(
                        self.aps[L][i + nslp[L]], funcs)
                    astr = self.gridSol + "_" + str(
                        self.cases[L][rank][i]) + "_m_cd"
                    muspm[L][i] = -funcs[astr]
                    sumpm[L] += -funcs[astr]

            # compute mean and variance estimate from start up samples
            sum1.append(comm.allreduce(sump[L]))
            mus.append(comm.allgather(musp[L]))
            summ.append(comm.allreduce(sumpm[L]))
            musm.append(comm.allgather(muspm[L]))

            #import pdb; pdb.set_trace()

            # mean at each level
            E = numpy.zeros(L + 1)
            for l in range(L + 1):
                E[l] = (sum1[l] + summ[l]) / nslt[l]

            # variance at each level
            V = numpy.zeros(L + 1)
            for l in range(L + 1):
                sum2 = 0.
                for i in range(len(mus[l])):  #range(size):
                    for j in range(len(mus[l][i])):  #range(self.nsp):
                        if l > 0:
                            sum2 += ((mus[l][i][j] + musm[l][i][j]) - E[l])**2
                        else:
                            sum2 += (mus[l][i][j] - E[l])**2
                V[l] = sum2 / nslt[l]

            #import pdb; pdb.set_trace()
            # now determine the optimal number of samples at each level
            N1.append(0.)
            worksum = 0
            for l in range(L + 1):
                worksum += numpy.sqrt(V[l] * (M**l))
            for l in range(L + 1):
                nlf = 2 * numpy.sqrt(V[l] / (M**l)) * worksum / (eps * eps)
                nlfm = max(nslt[l], math.ceil(nlf))
                N1[l] = nlfm

            # now compute and generate additional samples at each level
            # first partition samples  NEVERMIND (just do it once at each level, no need to repeat)
            # create the extra number of solvers at each (the current) level

            # need to loop everything from here on

            for l in range(L + 1):
                alist = self.aps[l][0:nslp[l]]
                slist = self.solvers[l][0:nslp[l]]
                mlist = self.meshes[l][0:nslp[l]]
                if l > 0:
                    alist2 = self.aps[l][nslp[l]:]
                    slist2 = self.solvers[l][nslp[l]:]
                    mlist2 = self.meshes[l][nslp[l]:]

                self.naddedtot[l] = N1[l] - nslt[l]
                self.current_samples += self.naddedtot[l]
                #import pdb; pdb.set_trace()
                if rank == 0:
                    rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
                else:
                    rank0sam = None
                self.sample = comm.bcast(rank0sam, root=0)

                if self.naddedtot[l] > 0:
                    temp = divide_cases(self.naddedtot[l], size)
                    for i in range(len(temp)):
                        for j in range(len(temp[i])):
                            temp[i][j] += self.current_samples - self.naddedtot[
                                l]  #self.Lmax*self.NS0 + sum(self.naddedtot[0:L])
                else:
                    temp = []
                if len(temp):
                    for ns in range(size):
                        self.cases[l][ns] += temp[ns]  #append
                nslpnew = len(self.cases[l][rank])
                nsltnew = sum([len(self.cases[l][x]) for x in range(size)])
                #self.nsp[L] = len(self.cases[L][rank]) #int(ns/size) # samples per processor
                self.samplep[l] = self.sample[self.cases[l][rank]]

                for i in range(nslp[l],
                               nslpnew):  #need it to be just the extra cases
                    #import pdb; pdb.set_trace()
                    namestr = self.gridSol + "_" + str(self.cases[l][rank][i])

                    # create meshes
                    leveloptions = self.woptions
                    leveloptions['gridFile'] = self.meshnames[l]
                    mlist.append(
                        USMesh(options=leveloptions, comm=MPI.COMM_SELF))

                    # create aeroproblems
                    aloptions = self.aoptions
                    aloptions['gridFile'] = self.meshnames[l]
                    alist.append(
                        AeroProblem(name=namestr,
                                    alpha=alpha,
                                    mach=mach,
                                    reynolds=Re,
                                    reynoldsLength=Re_L,
                                    T=tempR,
                                    areaRef=arearef,
                                    chordRef=chordref,
                                    evalFuncs=['cd']))
                    time.sleep(
                        0.1)  # this solves a few problems for some reason
                    # create solvers
                    slist.append(ADFLOW(options=aloptions, comm=MPI.COMM_SELF))

                    saconstsm = self.samplep[l][i].tolist()
                    self.saconsts = saconstsm + self.saconstsb
                    slist[i].setOption('SAConsts', self.saconsts)
                    slist[i].setDVGeo(self.DVGeo)
                    slist[i].setMesh(mlist[i])
                    coords = slist[i].getSurfaceCoordinates(
                        groupName=slist[i].allWallsGroup)
                    slist[i].DVGeo.addPointSet(coords, 'coords')
                    time.sleep(0.1)
                    if l > 0:  #create additional solvers at higher levels for the estimators
                        # create meshes
                        #import pdb; pdb.set_trace()
                        namestr = self.gridSol + "_" + str(
                            self.cases[l][rank][i]) + "_m"
                        leveloptions = self.woptions
                        leveloptions['gridFile'] = self.meshnames[l - 1]
                        mlist2.append(
                            USMesh(options=leveloptions, comm=MPI.COMM_SELF))
                        # create aeroproblems
                        aloptions = self.aoptions
                        aloptions['gridFile'] = self.meshnames[l - 1]
                        alist2.append(
                            AeroProblem(name=namestr,
                                        alpha=alpha,
                                        mach=mach,
                                        reynolds=Re,
                                        reynoldsLength=Re_L,
                                        T=tempR,
                                        areaRef=arearef,
                                        chordRef=chordref,
                                        evalFuncs=['cd']))
                        time.sleep(
                            0.1)  # this solves a few problems for some reason
                        # create solvers
                        slist2.append(
                            ADFLOW(options=aloptions, comm=MPI.COMM_SELF))
                        slist2[i].setOption('SAConsts', self.saconsts)
                        slist2[i].setDVGeo(self.DVGeo)
                        slist2[i].setMesh(mlist2[i])
                        coords = slist[i].getSurfaceCoordinates(
                            groupName=slist2[i].allWallsGroup)
                        slist2[i].DVGeo.addPointSet(coords, 'coords')
                nslp[l] = nslpnew
                nslt[l] = nsltnew

                self.aps[l] = alist
                self.solvers[l] = slist
                self.meshes[l] = mlist
                if l > 0:
                    self.aps[l] += alist2
                    self.solvers[l] += slist2
                    self.meshes[l] += mlist2

                # compute remaining samples
                sump[l] = 0
                sumpm[l] = 0
                musp[l] = numpy.zeros(nslp[l])
                muspm[l] = numpy.zeros(nslp[l])
                for i in range(nslp[l]):
                    # just do this again in case
                    saconstsm = self.samplep[l][i].tolist()
                    self.saconsts = saconstsm + self.saconstsb
                    self.solvers[l][i].setOption('SAConsts', self.saconsts)
                    self.solvers[l][i].DVGeo.setDesignVars(dvdict)
                    self.aps[l][i].setDesignVars(dvdict)
                    self.solvers[l][i](self.aps[l][i])
                    self.solvers[l][i].evalFunctions(self.aps[l][i], funcs)
                    astr = self.gridSol + "_" + str(
                        self.cases[l][rank][i]) + "_cd"
                    musp[l][i] = funcs[astr]
                    sump[l] += funcs[astr]
                    #import pdb; pdb.set_trace()
                    if l > 0:
                        self.solvers[l][i + nslp[l]].setOption(
                            'SAConsts', self.saconsts)
                        self.solvers[l][i +
                                        nslp[l]].DVGeo.setDesignVars(dvdict)
                        self.aps[l][i + nslp[l]].setDesignVars(dvdict)
                        self.solvers[l][i + nslp[l]](self.aps[l][i + nslp[l]])
                        self.solvers[l][i + nslp[l]].evalFunctions(
                            self.aps[l][i + nslp[l]], funcs)
                        astr = self.gridSol + "_" + str(
                            self.cases[l][rank][i]) + "_m_cd"
                        muspm[l][i] = -funcs[astr]
                        sumpm[l] += -funcs[astr]

                # compute mean and variance estimate from all samples
                sum1[l] = comm.allreduce(sump[l])
                mus[l] = comm.allgather(musp[l])
                summ[l] = comm.allreduce(sumpm[l])
                musm[l] = comm.allgather(muspm[l])

                # mean at each level
                E[l] = (sum1[l] + summ[l]) / nslt[l]

                # variance at each level
                sum2 = 0.
                for i in range(len(mus[l])):  #range(size):
                    for j in range(len(mus[l][i])):  #range(self.nsp):
                        if l > 0:
                            sum2 += ((mus[l][i][j] + musm[l][i][j]) - E[l])**2
                        else:
                            sum2 += (mus[l][i][j] - E[l])**2
                V[l] = sum2 / nslt[l]

            # if L == 1:
            #     import pdb; pdb.set_trace()
            L += 1
        #import pdb; pdb.set_trace()
        #sys.stdout = sys.__stdout__
        if rank == 0:
            print("MLMC Completed, Samples per level: ", N1)
        self.N1 = N1
        #import pdb; pdb.set_trace()

        # test for convergence
        # don't actually need this, won't ever end early
        # range = -1:0
        # if L > 1 & M**L >= 4:
        #     con = M**(range.*suml(2,L+1+range)./suml(1,L+1+range))
        #     converged = (max(abs(con)) < (M-1)*eps/sqrt(2))

        # once done, we have aps, solvers, meshes, which is all we need

    def dist_samples(self):
        # If we already have the number of samples, just create as many solvers as needed at each level
        # Just do this after running MLMC() anyway

        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']  #0.95
        Re = self.ooptions['Re']  #50000
        Re_L = 1.0
        tempR = 540
        arearef = 2.0
        chordref = 1.0
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']

        self.current_samples = sum(self.N1)
        if rank == 0:
            rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
        else:
            rank0sam = None
        self.sample = comm.bcast(rank0sam, root=0)
        #import pdb; pdb.set_trace()
        # Scatter samples on each level, multi-point parallelism
        self.cases = []
        self.samplep = []
        for i in range(self.Lmax):
            self.cases.append(divide_cases(self.N1[i], size))
            for j in range(len(self.cases[i])):
                for k in range(len(self.cases[i][j])):
                    self.cases[i][j][k] += sum(self.N1[0:i])
            #self.nsp.append(len(self.cases[i][rank]))#int(ns/size) # samples per processor
            self.samplep.append(self.sample[self.cases[i][rank]])

        # Actually create solvers (and aeroproblems?) (and mesh?) now
        self.aps = []
        self.solvers = []
        self.meshes = []
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            alist = []
            slist = []
            mlist = []
            alist2 = []
            slist2 = []
            mlist2 = []
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            for i in range(nslp[k]):
                namestr = self.gridSol + "_" + str(self.cases[k][rank][i])

                # create meshes
                leveloptions = self.woptions
                leveloptions['gridFile'] = self.meshnames[k]
                mlist.append(USMesh(options=leveloptions, comm=MPI.COMM_SELF))

                # create aeroproblems
                aloptions = self.aoptions
                aloptions['gridFile'] = self.meshnames[k]
                alist.append(
                    AeroProblem(name=namestr,
                                alpha=alpha,
                                mach=mach,
                                reynolds=Re,
                                reynoldsLength=Re_L,
                                T=tempR,
                                areaRef=arearef,
                                chordRef=chordref,
                                evalFuncs=['cd']))
                time.sleep(0.1)  # this solves a few problems for some reason
                # create solvers
                slist.append(ADFLOW(options=aloptions, comm=MPI.COMM_SELF))

                # if not self.ooptions['run_once']:
                #     saconstsm = self.samplep[i].tolist()
                # else:
                saconstsm = self.samplep[k][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                slist[i].setOption('SAConsts', self.saconsts)
                slist[i].setDVGeo(self.DVGeo)
                slist[i].setMesh(mlist[i])
                coords = slist[i].getSurfaceCoordinates(
                    groupName=slist[i].allWallsGroup)
                slist[i].DVGeo.addPointSet(coords, 'coords')

                if k > 0:  #create additional solvers at higher levels for the estimators
                    # create meshes
                    namestr = self.gridSol + "_" + str(
                        self.cases[k][rank][i]) + "_m"
                    leveloptions = self.woptions
                    leveloptions['gridFile'] = self.meshnames[k - 1]
                    mlist2.append(
                        USMesh(options=leveloptions, comm=MPI.COMM_SELF))
                    # create aeroproblems
                    aloptions = self.aoptions
                    aloptions['gridFile'] = self.meshnames[k - 1]
                    alist2.append(
                        AeroProblem(name=namestr,
                                    alpha=alpha,
                                    mach=mach,
                                    reynolds=Re,
                                    reynoldsLength=Re_L,
                                    T=tempR,
                                    areaRef=arearef,
                                    chordRef=chordref,
                                    evalFuncs=['cd']))
                    time.sleep(
                        0.1)  # this solves a few problems for some reason
                    # create solvers
                    slist2.append(ADFLOW(options=aloptions,
                                         comm=MPI.COMM_SELF))
                    slist2[i].setOption('SAConsts', self.saconsts)
                    slist2[i].setDVGeo(self.DVGeo)
                    slist2[i].setMesh(mlist2[i])
                    coords = slist[i].getSurfaceCoordinates(
                        groupName=slist2[i].allWallsGroup)
                    slist2[i].DVGeo.addPointSet(coords, 'coords')

            self.aps.append(alist)
            self.solvers.append(slist)
            self.meshes.append(mlist)
            if k > 0:
                self.aps[k] += alist2
                self.solvers[k] += slist2
                self.meshes[k] += mlist2
Esempio n. 4
0
class PlateComponentMFMC(om.ExplicitComponent):
    """Robust Bump Flow Problem, with LHS Multi Level Monte Carlo Samples"""
    def initialize(self):
        # Need to modify this dictionary when we change the SA constants
        #import pdb; pdb.set_trace()
        #sys.stdout = open(os.devnull, "w")
        self.aoptions = aeroOptions
        self.woptions = warpOptions
        self.ooptions = optOptions
        self.uoptions = uqOptions

        self.Pr = 0.
        # Generate FFD and DVs
        if rank == 0:
            rank0dvg = pf.createFFD()
        else:
            rank0dvg = None
        self.DVGeo = comm.bcast(rank0dvg, root=0)

        # Get a full list of every mesh name we have, assume they are ordered properly by level
        self.meshnames = self.uoptions['gridFileLevels']
        self.Lmax = len(self.meshnames)  #as many levels as we have meshes
        self.mord = [*range(self.Lmax)]
        self.mord.reverse()

        if self.Lmax < 3:
            raise ValueError('Not enough meshes available, ' + self.Lmax +
                             ' < 3')

        # Spalart Allmaras model constants, to be changed in UQ (4 for now)
        saconstsm = [0.41, 0.1355, 0.622, 0.66666666667]
        self.saconstsb = [7.1, 0.3, 2.0, 1.0, 2.0, 1.2, 0.5, 2.0]
        self.saconsts = saconstsm + self.saconstsb
        self.aoptions['SAConsts'] = self.saconsts
        #self.gridSol = f'{meshname}_{saconstsm}_sol'
        solname = self.ooptions['prob_name']
        self.gridSol = f'{solname}_sol'

        self.cases = []
        #self.nsp = []   #keep track per level
        self.samplep = []
        self.naddedtot = []
        for i in range(self.Lmax):
            self.naddedtot.append(0)

        self.aps = []
        self.solvers = []
        self.meshes = []

        self.NS0 = self.uoptions['NS0']
        self.current_samples = self.NS0 * self.Lmax
        self.N1 = None
        self.a1 = None
        self.P = self.uoptions['P']
        ##########
        # call distribute samples here?
        if not self.uoptions['use-predetermined-samples']:
            self.MFMC()
        else:
            self.N1 = self.uoptions['predet-N1']
            self.a1 = self.uoptions['predet-a1']

        self.dist_samples()

        ##########

        # Set constraints, should only need one of those solvers, the meshes are all the same
        if rank == 0:
            self.DVCon = DVConstraints()
            self.DVCon2 = DVConstraints()
            self.DVCon.setDVGeo(
                self.solvers[0][0].DVGeo.getFlattenedChildren()[1])
            self.DVCon2.setDVGeo(self.solvers[0][0].DVGeo)

            self.DVCon.setSurface(
                self.solvers[0][0].getTriangulatedMeshSurface(
                    groupName='allSurfaces'))
            # set extra group for surface area condition
            self.DVCon2.setSurface(
                self.solvers[0][0].getTriangulatedMeshSurface(), name='wall')

            # DV should be same into page (not doing anything right now)
            #import pdb; pdb.set_trace()
            lIndex = self.solvers[0][0].DVGeo.getFlattenedChildren(
            )[1].getLocalIndex(0)
            indSetA = []
            indSetB = []
            nXc = optOptions['NX']
            self.NC = math.trunc(
                ((1.0 - self.ooptions['DVFraction']) * self.ooptions['NX']))
            ind = [
                int(nXc / 2) - int(self.NC / 2),
                int(nXc / 2) + int(self.NC / 2)
            ]
            for i in range(ind[0], ind[1]):
                indSetA.append(lIndex[i, 0, 1])
                indSetB.append(lIndex[i, 1, 1])
            # for i in range(lIndex.shape[0]):
            #     indSetA.append(lIndex[i, 0, 1])
            #     indSetB.append(lIndex[i, 1, 1])
            self.DVCon.addLinearConstraintsShape(indSetA,
                                                 indSetB,
                                                 factorA=1.0,
                                                 factorB=-1.0,
                                                 lower=0,
                                                 upper=0,
                                                 name='eqs')

            # Thickness constraints (one for each active DV)
            #import pdb; pdb.set_trace()

            # Maximum thickness of the domain, translates to minimum thickness of bump
            ub = 1.0 - self.ooptions['DCMinThick']
            tcf = self.ooptions['DCThickFrac']
            ra = self.ooptions['bumpBounds']
            lim = self.ooptions['DCMinArea']
            span = numpy.linspace(0, 1, nXc)
            xc = span * (ra[1] - ra[0]) + ra[0]
            #ind = range(int(nXc/2) - int(self.NC/2), int(nXc/2) + int(self.NC/2)))
            ind = [
                int(nXc / 2) - int(tcf * self.NC / 2),
                int(nXc / 2) + int(tcf * self.NC / 2)
            ]
            ptList = numpy.zeros([2, 3])
            ptList[:, 0] = xc[ind]
            ptList[:, 1] = 0.5
            ptList[:, 2] = 0.5

            if self.ooptions['use_area_con']:
                self.DVCon2.addSurfaceAreaConstraint(lower=lim,
                                                     upper=10.,
                                                     name='sas',
                                                     surfaceName='wall')
            else:
                self.DVCon2.addThicknessConstraints1D(ptList,
                                                      self.NC, [0, 0, 1],
                                                      lower=0.5,
                                                      upper=ub,
                                                      name='tcs')

        dummy = rank
        dsum = comm.allgather(dummy)

        sys.stdout = sys.__stdout__

    def setup(self):
        #initialize shape and set deformation points as inputs
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']
        # mult = numpy.linspace(1.0,1.5,num=int(0.5*len(a_init['pnts'])))
        # mult = numpy.concatenate((mult, mult))
        #if self.ooptions['run_once']:
        #    a_init['pnts'] = self.ooptions['ro_shape']
        #a_init['pnts'] = numpy.multiply(mult, a_init['pnts'])
        self.add_input('a', a_init['pnts'], desc="Bump Shape Control Points")
        #self.add_input('a', 0.2, desc="Bump Shape Control Points")

        if self.ooptions['use_area_con']:
            self.add_output('SA', 1.0, desc='Surface Area Constraint')
        else:
            self.add_output('TC',
                            numpy.zeros(self.NC),
                            desc='Thickness Constraints')
        self.add_output('Cd_m', 0.0, desc="Mean Drag Coefficient")
        self.add_output('Cd_v', 0.0, desc="Variance Drag Coefficient")
        self.add_output('Cd_r', 0.0, desc="Robust Drag Objective")
        self.add_output('EQ',
                        numpy.zeros(int(len(a_init['pnts']) / 2)),
                        desc="Control Point Symmetry")

        self.add_output('N1', self.N1, desc="MFMC Samples at each level")
        self.add_output('a1', self.a1, desc="MFMC Coeffs at each level")
        self.add_output('Pr', 0., desc="MFMC Samples at each level")

    def setup_partials(self):
        self.declare_partials('Cd_m', 'a', method='exact')
        self.declare_partials('Cd_v', 'a', method='exact')
        self.declare_partials('Cd_r', 'a', method='exact')
        if self.ooptions['use_area_con']:
            self.declare_partials('SA', 'a', method='exact')
        else:
            self.declare_partials('TC', 'a', method='exact')
        self.declare_partials('EQ', 'a', method='exact')

    def compute(self, inputs, outputs):
        # run the bump shape model
        #import pdb; pdb.set_trace()

        dvdict = {'pnts': inputs['a']}
        ntot = self.current_samples
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
        funcs = {}
        sump = []
        musp = []
        sum1 = []
        mus = []
        sumpm = []
        muspm = []
        summ = []
        musm = []
        E = numpy.zeros(self.Lmax)
        V = numpy.zeros(self.Lmax)
        for k in range(self.Lmax):
            sump.append(0.0)
            musp.append(numpy.zeros(nslp[k]))
            sumpm.append(0.)
            if k > 0:
                muspm.append(numpy.zeros(nslp[k - 1]))
            else:
                muspm.append(numpy.zeros(nslp[k]))  #not used
            for i in range(nslp[k]):
                saconstsm = self.samplep[self.Lmax - 1][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[k][i].setOption('SAConsts', self.saconsts)
                self.solvers[k][i].DVGeo.setDesignVars(dvdict)
                self.aps[k][i].setDesignVars(dvdict)
                self.solvers[k][i](self.aps[k][i])
                self.solvers[k][i].evalFunctions(self.aps[k][i], funcs)
                astr = self.gridSol + "_" + str(self.cases[k][rank][i]) + "_cd"
                musp[k][i] = funcs[astr]
                sump[k] += funcs[astr]
                if k > 0 and i < nslp[k - 1]:
                    muspm[k][i] = -funcs[astr]
                    sumpm[k] += -funcs[astr]

        # compute mean and variance
        for k in range(self.Lmax):
            sum1.append(comm.allreduce(sump[k]))
            mus.append(comm.allgather(musp[k]))
            summ.append(comm.allreduce(sumpm[k]))
            musm.append(comm.allgather(muspm[k]))
            #import pdb; pdb.set_trace()
            if k > 0:
                E[k] = (sum1[k] / nslt[k] + summ[k] / nslt[k - 1])
            else:
                E[k] = (sum1[k] / nslt[k])
            sum2 = 0.
            for i in range(len(mus[k])):  #loop over processors
                for j in range(len(
                        mus[k][i])):  #loop over samples on processors
                    if k > 0:
                        #sum2 = 0

                        sum2 += (
                            (mus[k][i][j] - sum1[k] / nslt[k])**2) / nslt[k]
                        if j < len(mus[k - 1][i]):
                            sum2 -= ((musm[k][i][j] - summ[k] / nslt[k - 1])**
                                     2) / nslt[k - 1]

                    else:
                        sum2 += ((mus[k][i][j] - E[k])**2) / nslt[k]
            V[k] = sum2

        #import pdb; pdb.set_trace()
        if rank == 0:
            self.DVCon.evalFunctions(funcs, includeLinear=True)
            self.DVCon2.evalFunctions(funcs, includeLinear=True)
            eq0 = funcs['eqs']
            sa0 = funcs['sas']
        else:
            eq0 = None
            sa0 = None
        eq = comm.bcast(eq0, root=0)
        sa = comm.bcast(sa0, root=0)

        outputs['Cd_m'] = numpy.dot(self.a1, E)
        outputs['Cd_v'] = numpy.dot(self.a1, V)  #by assumption
        rho = self.uoptions['rho']
        outputs['Cd_r'] = numpy.dot(self.a1,
                                    E) + rho * math.sqrt(numpy.dot(self.a1, V))
        #import pdb; pdb.set_trace()
        if self.ooptions['use_area_con']:
            outputs['SA'] = sa
        else:
            outputs['TC'] = funcs['tcs']

        outputs['EQ'] = eq
        outputs['N1'] = self.N1
        outputs['a1'] = self.a1
        outputs['Pr'] = self.Pr
        #outputs['Cd'] = inputs['a']*inputs['a']

    def compute_partials(self, inputs, J):

        dvdict = {'pnts': inputs['a']}
        funcs = {}
        funcSens = {}
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
        sump = []
        musp = []
        sum1 = []
        sum2 = []
        mus = []

        sumpm = []
        muspm = []
        summ = []
        musm = []

        pmup = []
        psump = []  #numpy.zeros(len(inputs['a']))
        pmu = []
        psum1 = []

        pmupm = []
        psumpm = []  #numpy.zeros(len(inputs['a']))
        pmum = []
        psumm = []

        E = numpy.zeros(self.Lmax)
        V = numpy.zeros(self.Lmax)
        #import pdb; pdb.set_trace()
        for k in range(self.Lmax):
            sump.append(0.0)
            musp.append(numpy.zeros(nslp[k]))
            sumpm.append(0.)
            if k > 0:
                muspm.append(numpy.zeros(nslp[k - 1]))
            else:
                muspm.append(numpy.zeros(nslp[k]))  #not used
            pmup.append([])
            psump.append(numpy.zeros(len(inputs['a'])))
            pmupm.append([])
            psumpm.append(numpy.zeros(len(inputs['a'])))
            for i in range(nslp[k]):
                saconstsm = self.samplep[k][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[k][i].setOption('SAConsts', self.saconsts)
                self.solvers[k][i].DVGeo.setDesignVars(dvdict)
                self.aps[k][i].setDesignVars(dvdict)
                self.solvers[k][i](self.aps[k][i])
                self.solvers[k][i].evalFunctions(self.aps[k][i], funcs)
                self.solvers[k][i].evalFunctionsSens(self.aps[k][i], funcSens,
                                                     ['cd'])
                astr = self.gridSol + "_" + str(self.cases[k][rank][i]) + "_cd"
                arr = [
                    x * (self.a1[k] / nslt[k]) for x in funcSens[astr]['pnts']
                ]
                musp[k][i] = funcs[astr]
                sump[k] += funcs[astr]
                pmup[k].append(arr[0])
                psump[k] += arr[0]
                if k > 0 and i < nslp[k - 1]:
                    arrm = [
                        x * (self.a1[k] / nslt[k - 1])
                        for x in funcSens[astr]['pnts']
                    ]
                    muspm[k][i] = -funcs[astr]
                    sumpm[k] += -funcs[astr]
                    pmupm[k].append(-arrm[0])
                    psumpm[k] -= arrm[0]

        # compute mean and variance
        psum2 = numpy.zeros(len(inputs['a']))
        for k in range(self.Lmax):
            sum1.append(comm.allreduce(sump[k]))
            mus.append(comm.allgather(musp[k]))
            summ.append(comm.allreduce(sumpm[k]))
            musm.append(comm.allgather(muspm[k]))
            psum1.append(comm.allreduce(psump[k]))
            pmu.append(comm.allgather(pmup[k]))
            psumm.append(comm.allreduce(psumpm[k]))
            pmum.append(comm.allgather(pmupm[k]))

            if k > 0:
                E[k] = (sum1[k] / nslt[k] + summ[k] / nslt[k - 1])
            else:
                E[k] = (sum1[k] / nslt[k])
            sum2 = 0.
            for i in range(len(mus[k])):  #loop over processors
                for j in range(len(
                        mus[k][i])):  #loop over samples on processors
                    if k > 0:
                        #sum2 = 0

                        sum2 += (
                            (mus[k][i][j] - sum1[k] / nslt[k])**2) / nslt[k]
                        if j < len(mus[k - 1][i]):
                            sum2 -= ((musm[k][i][j] - summ[k] / nslt[k - 1])**
                                     2) / nslt[k - 1]

                    else:
                        sum2 += ((mus[k][i][j] - E[k])**2) / nslt[k]
            V[k] = sum2

        # compute variance sensitivity
        psum2 = []
        for k in range(self.Lmax):
            psum2.append(numpy.zeros(len(inputs['a'])))
            for i in range(len(mus[0])):  #loop over processors
                for j in range(len(
                        mus[0][i])):  #loop over samples on processors
                    if k > 0:
                        temp = pmu[k][i][j] * nslt[k] - psum1[k]
                        arr2 = [
                            x * 2 * (mus[k][i][j] - sum1[k]) / nslt[k]
                            for x in temp
                        ]
                        psum2[k] += arr2
                        if j < len(mus[k - 1][i]):
                            temp = pmu[k][i][j] * nslt[k - 1] - psum1[k]
                            arr2 = [
                                x * 2 *
                                (musm[k][i][j] - summ[k] / nslt[k - 1]) /
                                nslt[k - 1] for x in temp
                            ]
                            psum2[k] -= arr2
                    else:
                        temp = pmu[k][i][j] * nslt[k] - psum1[k]
                        arr2 = [
                            x * 2 * (mus[k][i][j] - E[k]) / nslt[k]
                            for x in temp
                        ]
                        psum2[k] += arr2

        #import pdb; pdb.set_trace()
        if rank == 0:
            self.DVCon.evalFunctionsSens(funcSens, includeLinear=True)
            self.DVCon2.evalFunctionsSens(funcSens, includeLinear=True)
            eq0p = funcSens['eqs']['pnts']
            sa0p = funcSens['sas']['pnts']
        else:
            eq0p = None
            sa0p = None
        eqp = comm.bcast(eq0p, root=0)
        sap = comm.bcast(sa0p, root=0)

        J['Cd_m', 'a'] = sum(psum1) + sum(psumm)
        J['Cd_v', 'a'] = sum(psum2)
        rho = self.uoptions['rho']
        J['Cd_r', 'a'] = sum(psum1) + sum(psumm) + rho * (
            1. / (2 * math.sqrt(numpy.dot(self.a1, V)))) * sum(psum2)
        if self.ooptions['use_area_con']:
            J['SA', 'a'] = sap
        else:
            J['TC', 'a'] = funcSens['tcs']['pnts']
        J['EQ', 'a'] = eqp

    #J['Cd','a'][0] = 2*inputs['a']

    def MFMC(self):
        # Use an MFMC algorithm to determine optimal sample distribution and coefficients among mesh levels
        # We do this once before optimization, then compute statistics with the same set of samples and coeffs at every iteration

        # start with initial samples
        # Get a set of UQ sample points (LHS), enough for each level at the start
        #sys.stdout = open(os.devnull, "w")

        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']  #0.95
        Re = self.ooptions['Re']  #50000
        Re_L = 1.0
        tempR = 540
        arearef = 2.0
        chordref = 1.0
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']

        self.current_samples = self.NS0 * self.Lmax
        if rank == 0:
            rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
        else:
            rank0sam = None
        self.sample = comm.bcast(rank0sam, root=0)

        N1 = []
        a1 = numpy.zeros(self.Lmax)
        r1 = numpy.zeros(self.Lmax)

        # Scatter samples on each level, multi-point parallelism

        for i in range(self.Lmax):
            self.cases.append(divide_cases(self.NS0, size))
            for j in range(len(self.cases[i])):
                for k in range(len(self.cases[i][j])):
                    self.cases[i][j][k] += i * self.NS0
            #self.nsp.append(len(self.cases[i][rank]))#int(ns/size) # samples per processor
            self.samplep.append(self.sample[self.cases[i][rank]])
        #import pdb; pdb.set_trace()
        #self.samplep = self.sample[self.cases[rank]]#self.sample[(rank*self.nsp):(rank*self.nsp+(self.nsp))] #shouldn't really need to "scatter" per se
        #import pdb; pdb.set_trace()
        # for i in range(self.Lmax):
        #     assert len(self.samplep[i]) == self.nsp[i]

        # Create solvers for the preliminary data
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            alist = []
            slist = []
            mlist = []
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            for i in range(nslp[k]):
                namestr = self.gridSol + "_" + str(self.cases[k][rank][i])

                # create meshes
                leveloptions = self.woptions
                leveloptions['gridFile'] = self.meshnames[self.mord[k]]
                #import pdb; pdb.set_trace()
                mlist.append(USMesh(options=leveloptions, comm=MPI.COMM_SELF))

                # create aeroproblems
                aloptions = self.aoptions
                aloptions['gridFile'] = self.meshnames[self.mord[k]]
                alist.append(
                    AeroProblem(name=namestr,
                                alpha=alpha,
                                mach=mach,
                                reynolds=Re,
                                reynoldsLength=Re_L,
                                T=tempR,
                                areaRef=arearef,
                                chordRef=chordref,
                                evalFuncs=['cd']))
                time.sleep(0.1)  # this solves a few problems for some reason
                # create solvers
                slist.append(ADFLOW(options=aloptions, comm=MPI.COMM_SELF))

                # if not self.ooptions['run_once']:
                #     saconstsm = self.samplep[i].tolist()
                # else:
                saconstsm = self.samplep[0][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                slist[i].setOption('SAConsts', self.saconsts)
                slist[i].setDVGeo(self.DVGeo)
                slist[i].setMesh(mlist[i])
                coords = slist[i].getSurfaceCoordinates(
                    groupName=slist[i].allWallsGroup)
                slist[i].DVGeo.addPointSet(coords, 'coords')

            self.aps.append(alist)
            self.solvers.append(slist)
            self.meshes.append(mlist)

        # Solve the preliminary samples

        # start looping over mesh levels
        sumt = []
        sumtp = []
        nslp = []
        nslt = []
        sum1 = []
        mus = []
        sump = []
        musp = []
        sumpm = []
        muspm = []
        summ = []
        musm = []
        Et = numpy.zeros(self.Lmax)
        E = numpy.zeros(self.Lmax)
        V = numpy.zeros(self.Lmax)
        S = numpy.zeros(self.Lmax)
        N1 = []
        for k in range(self.Lmax):
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            dvdict = {'pnts': a_init['pnts']}
            funcs = {}
            sumtp.append(0.0)
            sump.append(0.)
            musp.append(numpy.zeros(nslp[k]))
            sumpm.append(0.)
            muspm.append(numpy.zeros(nslp[k]))

            for i in range(nslp[k]):
                # just do this again in case
                saconstsm = self.samplep[0][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                self.solvers[k][i].setOption('SAConsts', self.saconsts)
                self.solvers[k][i].DVGeo.setDesignVars(dvdict)
                self.aps[k][i].setDesignVars(dvdict)
                pc0 = time.process_time()
                self.solvers[k][i](self.aps[k][i])
                self.solvers[k][i].evalFunctions(self.aps[k][i], funcs)
                pc1 = time.process_time()
                astr = self.gridSol + "_" + str(self.cases[k][rank][i]) + "_cd"
                musp[k][i] = funcs[astr]
                sump[k] += funcs[astr]
                sumtp[k] += (pc1 - pc0)

        # compute mean and variance estimate from start up samples
        for k in range(self.Lmax):
            sumt.append(comm.allreduce(sumtp[k]))
            sum1.append(comm.allreduce(sump[k]))
            mus.append(comm.allgather(musp[k]))
            summ.append(comm.allreduce(sumpm[k]))
            musm.append(comm.allgather(muspm[k]))
            mus[k] = numpy.concatenate(mus[k][:])
            musm[k] = numpy.concatenate(musm[k][:])
            #import pdb; pdb.set_trace()
            # mean at each level
            Et[k] = sumt[k] / nslt[k]
            E[k] = (sum1[k]) / nslt[k]  #+summ[k]
            sum2 = 0.
            for i in range(len(mus[k])):  #loop over processors
                sum2 += (mus[k][i] - E[k])**2
            V[k] = sum2 / nslt[k]
            S[k] = math.sqrt(V[k])

        # compute correlation matrix and rearrange models if necessary
        ordered = False
        while not ordered:
            rho = numpy.corrcoef(mus)
            ordered = True  # check if contradicted
            #tarr = rho[0,1:]
            for k in range(self.Lmax - 2):
                test = rho[0, 1 + k]**2 - rho[0, 2 + k]**2
                if test < 0:
                    ordered = False
            tarr = -rho[0, :]**2

            if not ordered:
                sind = numpy.argsort(tarr)
                #import pdb; pdb.set_trace()
                self.mord[:] = [self.mord[i] for i in sind]
                E[:] = [E[i] for i in sind]
                Et[:] = [Et[i] for i in sind]
                V[:] = [V[i] for i in sind]
                S[:] = [S[i] for i in sind]
                mus[:] = [mus[i] for i in sind]

        # now compute N1 and a1 using sigma, rho, w, and p
        for k in range(self.Lmax):
            a1[k] = S[0] * rho[0, k] / S[k]

            if k == 0:
                r1[k] = 1
            elif k == self.Lmax - 1:
                work = Et[0] * (rho[0, k]**2)
                work /= Et[k] * (1 - rho[0, 1]**2)
                r1[k] = math.sqrt(work)
            else:
                work = Et[0] * (rho[0, k - 1]**2 - rho[0, k]**2)
                work /= Et[k] * (1 - rho[0, 1]**2)
                r1[k] = math.sqrt(work)

        for k in range(self.Lmax):
            N1.append(0)

        nsf = self.P / numpy.dot(Et, r1)
        N1[0] = math.ceil(nsf)
        for k in range(self.Lmax):
            nsf = N1[0] * r1[k]
            N1[k] = math.ceil(nsf)

        # limit the number of samples on the last one to pass the sanity check, for debug
        sanity = numpy.dot(N1, Et)
        if sanity > 1.2 * self.P:
            N1n = (self.P - numpy.dot(N1[0:self.Lmax - 2],
                                      Et[0:self.Lmax - 2])) / Et[self.Lmax - 1]
            N1[self.Lmax - 1] = math.ceil(N1n)

        self.Pr = numpy.dot(N1, Et)

        self.N1 = N1
        self.a1 = a1
        #import pdb; pdb.set_trace()
        if rank == 0:
            print("MFMC Completed, Samples per level: ", N1)

        # call dist_samples after this

    def dist_samples(self):
        # If we already have the number of samples, just create as many solvers as needed at each level
        # Just do this after running MLMC() anyway

        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']  #0.95
        Re = self.ooptions['Re']  #50000
        Re_L = 1.0
        tempR = 540
        arearef = 2.0
        chordref = 1.0
        a_init = self.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']

        self.current_samples = sum(self.N1)
        if rank == 0:
            rank0sam = plate_sa_lhs.genLHS(s=self.current_samples)
        else:
            rank0sam = None
        self.sample = comm.bcast(rank0sam, root=0)
        #import pdb; pdb.set_trace()
        # Scatter samples on each level, multi-point parallelism
        self.cases = []
        self.samplep = []
        for i in range(self.Lmax):
            self.cases.append(divide_cases(self.N1[i], size))
            for j in range(len(self.cases[i])):
                for k in range(len(self.cases[i][j])):
                    self.cases[i][j][k] += sum(self.N1[0:i])
            #self.nsp.append(len(self.cases[i][rank]))#int(ns/size) # samples per processor
            self.samplep.append(self.sample[self.cases[i][rank]])

        # Actually create solvers (and aeroproblems?) (and mesh?) now
        self.aps = []
        self.solvers = []
        self.meshes = []
        nslp = []
        nslt = []
        for k in range(self.Lmax):
            alist = []
            slist = []
            mlist = []
            nslp.append(len(self.cases[k][rank]))
            nslt.append(sum([len(self.cases[k][x]) for x in range(size)]))
            for i in range(nslp[k]):
                namestr = self.gridSol + "_" + str(self.cases[k][rank][i])

                # create meshes
                leveloptions = self.woptions
                leveloptions['gridFile'] = self.meshnames[self.mord[k]]
                mlist.append(USMesh(options=leveloptions, comm=MPI.COMM_SELF))

                # create aeroproblems
                aloptions = self.aoptions
                aloptions['gridFile'] = self.meshnames[self.mord[k]]
                alist.append(
                    AeroProblem(name=namestr,
                                alpha=alpha,
                                mach=mach,
                                reynolds=Re,
                                reynoldsLength=Re_L,
                                T=tempR,
                                areaRef=arearef,
                                chordRef=chordref,
                                evalFuncs=['cd']))
                time.sleep(0.1)  # this solves a few problems for some reason
                # create solvers
                slist.append(ADFLOW(options=aloptions, comm=MPI.COMM_SELF))

                saconstsm = self.samplep[self.Lmax - 1][i].tolist()
                self.saconsts = saconstsm + self.saconstsb
                slist[i].setOption('SAConsts', self.saconsts)
                slist[i].setDVGeo(self.DVGeo)
                slist[i].setMesh(mlist[i])
                coords = slist[i].getSurfaceCoordinates(
                    groupName=slist[i].allWallsGroup)
                slist[i].DVGeo.addPointSet(coords, 'coords')

            self.aps.append(alist)
            self.solvers.append(slist)
            self.meshes.append(mlist)
Esempio n. 5
0
class PlateComponent(om.ExplicitComponent):
    """Deterministic Bump Flow Problem"""
    def initialize(self):
        # Need to modify this dictionary when we change the SA constants
        sys.stdout = open(os.devnull, "w")
        self.aoptions = aeroOptions
        self.woptions = warpOptions
        self.ooptions = optOptions

        # Generate FFD and DVs
        self.DVGeo = pf.createFFD()

        # starting flat mesh
        meshname = self.aoptions['gridFile']
        gridFile = meshname
        
        # flow characteristics
        alpha = 0.0
        mach = self.ooptions['mach']#0.95
        Re = self.ooptions['Re']#50000
        Re_L = 1.0
        temp = 540
        arearef = 2.0
        chordref = 1.0

        # Spalart Allmaras model constants, to be changed in UQ
        saconstsm = [0.41, 0.1355, 0.622, 0.66666666667, 7.1, 0.3, 2.0]
        self.saconsts = saconstsm + [1.0, 2.0, 1.2, 0.5, 2.0]
        self.aoptions['SAConsts'] = self.saconsts
        #self.gridSol = f'{meshname}_{saconstsm}_sol'
        solname = self.ooptions['prob_name']
        self.gridSol = f'{solname}_sol'

        # Aerodynamic problem description
        self.ap = AeroProblem(name=self.gridSol, alpha=alpha, mach=mach, reynolds=Re, reynoldsLength=Re_L, T=temp, areaRef=arearef, chordRef=chordref, 
        evalFuncs=['cd'])

        # Create solver
        self.CFDSolver = ADFLOW(options=self.aoptions, comm=MPI.COMM_WORLD)
        self.CFDSolver.setDVGeo(self.DVGeo)

        # Set up mesh warping
        self.mesh = USMesh(options=self.woptions, comm=MPI.COMM_WORLD)
        self.CFDSolver.setMesh(self.mesh)

        # Try setting the DVGeo coordinates here
        coords = self.CFDSolver.getSurfaceCoordinates(groupName=self.CFDSolver.allWallsGroup)
        self.CFDSolver.DVGeo.addPointSet(coords, 'coords')
        self.CFDSolver.DVGeo.getFlattenedChildren()[1].writePlot3d("ffdp_opt_def.xyz")


        # Set constraints
        self.DVCon = DVConstraints()
        self.DVCon2 = DVConstraints()
        self.DVCon.setDVGeo(self.CFDSolver.DVGeo.getFlattenedChildren()[1])
        self.DVCon2.setDVGeo(self.CFDSolver.DVGeo)

        self.DVCon.setSurface(self.CFDSolver.getTriangulatedMeshSurface(groupName='allSurfaces'))
        # set extra group for surface area condition
        self.DVCon2.setSurface(self.CFDSolver.getTriangulatedMeshSurface(), name='wall')

        # DV should be same into page (not doing anything right now)
        #import pdb; pdb.set_trace()
        lIndex = self.CFDSolver.DVGeo.getFlattenedChildren()[1].getLocalIndex(0)
        indSetA = []
        indSetB = []
        nXc = optOptions['NX']
        self.NC = math.trunc(((1.0 - self.ooptions['DVFraction'])*self.ooptions['NX']))
        ind = [int(nXc/2) - int(self.NC/2), int(nXc/2) + int(self.NC/2)]
        for i in range(ind[0], ind[1]):
            indSetA.append(lIndex[i, 0, 1])
            indSetB.append(lIndex[i, 1, 1])
        # for i in range(lIndex.shape[0]):
        #     indSetA.append(lIndex[i, 0, 1])
        #     indSetB.append(lIndex[i, 1, 1])
        self.DVCon.addLinearConstraintsShape(indSetA, indSetB, factorA=1.0, factorB=-1.0, lower=0, upper=0, name='eqs')

        # Thickness constraints (one for each active DV)
        #import pdb; pdb.set_trace()

        # Maximum thickness of the domain, translates to minimum thickness of bump
        ub = 1.0 - self.ooptions['DCMinThick']
        tcf = self.ooptions['DCThickFrac']
        ra = self.ooptions['bumpBounds']
        lim = self.ooptions['DCMinArea']
        span = numpy.linspace(0, 1, nXc)
        xc = span * (ra[1] - ra[0]) + ra[0]
        #ind = range(int(nXc/2) - int(self.NC/2), int(nXc/2) + int(self.NC/2)))
        ind = [int(nXc/2) - int(tcf*self.NC/2), int(nXc/2) + int(tcf*self.NC/2)]
        ptList = numpy.zeros([2, 3])
        ptList[:,0] = xc[ind]
        ptList[:,1] = 0.5
        ptList[:,2] = 0.5

        if self.ooptions['use_area_con']:
            self.DVCon2.addSurfaceAreaConstraint(lower=lim, upper=10., name='sas', surfaceName='wall')
        else:
            self.DVCon2.addThicknessConstraints1D(ptList, self.NC, [0,0,1], lower=0.5, upper=ub, name='tcs')

        sys.stdout = sys.__stdout__

    def setup(self):
        #initialize shape and set deformation points as inputs
        a_init = self.CFDSolver.DVGeo.getValues()
        a_init['pnts'][:] = self.ooptions['DVInit']
        # mult = numpy.linspace(1.0,1.5,num=int(0.5*len(a_init['pnts'])))
        # mult = numpy.concatenate((mult, mult))
        # a_init['pnts'] = numpy.multiply(mult, a_init['pnts'])
        #if self.ooptions['run_once']:
        #    a_init['pnts'] = self.ooptions['ro_shape']
        self.add_input('a', a_init['pnts'], desc="Bump Shape Control Points")
        #self.add_input('a', 0.2, desc="Bump Shape Control Points")

        if self.ooptions['use_area_con']:
            self.add_output('SA', 1.0, desc='Surface Area Constraint')
        else:
            self.add_output('TC', numpy.zeros(self.NC), desc='Thickness Constraints')
        self.add_output('Cd', 0.0, desc="Drag Coefficient")
        self.add_output('EQ', numpy.zeros(int(len(a_init['pnts'])/2)), desc="Control Point Symmetry")


    
    def setup_partials(self):
        self.declare_partials('Cd','a', method='exact')
        if self.ooptions['use_area_con']:
            self.declare_partials('SA','a', method='exact')
        else:
            self.declare_partials('TC','a', method='exact')
        self.declare_partials('EQ','a', method='exact')
    
    def compute(self, inputs, outputs):
        # run the bump shape model

        # move the mesh
        #import pdb; pdb.set_trace()
        dvdict = {'pnts':inputs['a']}
        self.CFDSolver.DVGeo.setDesignVars(dvdict)
        self.ap.setDesignVars(dvdict)

        #self.CFDSolver.DVGeo.update("coords")

        # Solve and evaluate functions
        funcs = {}
        self.CFDSolver(self.ap)
        self.DVCon.evalFunctions(funcs, includeLinear=True)
        self.DVCon2.evalFunctions(funcs, includeLinear=True)
        self.CFDSolver.evalFunctions(self.ap, funcs)

        str = self.gridSol + '_cd'
        outputs['Cd'] = funcs[str]
        if self.ooptions['use_area_con']:
            outputs['SA'] = funcs['sas']
        else:
            outputs['TC'] = funcs['tcs']
        
        outputs['EQ'] = funcs['eqs']

        #outputs['Cd'] = inputs['a']*inputs['a']

    def compute_partials(self, inputs, J):

        # move the mesh
        #import pdb; pdb.set_trace()
        dvdict = {'pnts':inputs['a']}
        self.CFDSolver.DVGeo.setDesignVars(dvdict)
        self.ap.setDesignVars(dvdict)
        #self.CFDSolver.DVGeo.update("coords")
 
 
        funcSens = {}
        #self.CFDSolver(self.ap) #ASSUME WE ALREADY COMPUTED THE SOLUTION
        self.DVCon.evalFunctionsSens(funcSens, includeLinear=True)
        self.DVCon2.evalFunctionsSens(funcSens, includeLinear=True)
        self.CFDSolver.evalFunctionsSens(self.ap, funcSens, ['cd'])
 
        str = self.gridSol + '_cd'
        J['Cd','a'] = funcSens[str]['pnts']
        if self.ooptions['use_area_con']:
            J['SA','a'] = funcSens['sas']['pnts']
        else:
            J['TC','a'] = funcSens['tcs']['pnts']
        J['EQ','a'] = funcSens['eqs']['pnts']