def set_functions(self,scenario,bodies): if self.tacs_proc: self.funclist = [] self.functag = [] for func in scenario.functions: if func.analysis_type != 'structural': # use mass as a placeholder for nonstructural functions self.funclist.append(functions.StructuralMass(self.tacs)) self.functag.append(0) elif func.name.lower() == 'ksfailure': if func.options: ksweight = func.options['ksweight'] if 'ksweight' in func.options else 50.0 else: ksweight = 50.0 self.funclist.append(functions.KSFailure(self.tacs, ksweight)) self.functag.append(1) elif func.name.lower() == 'compliance': self.funclist.append(functions.Compliance(self.tacs)) self.functag.append(1) elif func.name == 'mass': self.funclist.append(functions.StructuralMass(self.tacs)) self.functag.append(-1) else: print('WARNING: Unknown function being set into TACS set to mass') self.funclist.append(functions.StructuralMass(self.tacs)) self.functag.append(-1)
def get_coordinate_derivatives(self, scenario, bodies, step): """ Evaluate gradients with respect to structural design variables""" if self.tacs_proc: fXptSens_vec = self.tacs.createNodeVec() # get the list of indices for this body for ibody, body in enumerate(bodies): if body.shape: nodes = self.tacs_flex_bodies[ibody].dist_nodes # TACS does the summation over steps internally, so only get the values when evaluating initial conditions if step == 0: # find the coordinate derivatives for each function for nfunc, func in enumerate(scenario.functions): if func.adjoint: fXptSens_vec = self.integrator[ scenario.id].getXptGradient(nfunc) elif func.name == 'mass': tacsfunc = functions.StructuralMass(self.tacs) self.tacs.evalXptSens(tacsfunc, fXptSens_vec) # pick out the points for this body fxptSens = fXptSens_vec.getArray() for i, n in enumerate(nodes): body.struct_shape_term[3 * i:3 * i + 3, nfunc] += fxptSens[3 * n:3 * n + 3] return
def set_functions(self, scenario, bodies): if self.tacs_proc: self.funclist = [] self.functag = [] for func in scenario.functions: if func.analysis_type != 'structural': self.funclist.append(None) self.functag.append(0) elif func.name.lower() == 'ksfailure': if func.options: ksweight = func.options[ 'ksweight'] if 'ksweight' in func.options else 50.0 else: ksweight = 50.0 self.funclist.append( functions.KSFailure(self.assembler, ksWeight=ksweight)) self.functag.append(1) elif func.name.lower() == 'compliance': self.funclist.append(functions.Compliance(self.assembler)) self.functag.append(1) elif func.name.lower() == 'temperature': self.funclist.append( functions.AverageTemperature(self.assembler, volume=self.vol)) self.functag.append(1) elif func.name.lower() == 'heatflux': self.funclist.append(functions.HeatFlux(self.assembler)) self.functag.append(1) elif func.name == 'mass': self.funclist.append( functions.StructuralMass(self.assembler)) self.functag.append(-1) else: print( 'WARNING: Unknown function being set into TACS set to mass' ) self.funclist.append( functions.StructuralMass(self.assembler)) self.functag.append(-1) return
def create_problem(forest, bcs, props, nlevels): """ Create the TMRTopoProblem object and set up the topology optimization problem. This code is given the forest, boundary conditions, material properties and the number of multigrid levels. Based on this info, it creates the TMRTopoProblem and sets up the mass-constrained compliance minimization problem. Before the problem class is returned it is initialized so that it can be used for optimization. Args: forest (OctForest): Forest object bcs (BoundaryConditions): Boundary condition object props (StiffnessProperties): Material properties object nlevels (int): number of multigrid levels Returns: TopoProblem: Topology optimization problem instance """ # Create the problem and filter object obj = CreatorCallback(bcs, props) problem = TopOptUtils.createTopoProblem(forest, obj.creator_callback, filter_type, nlevels=nlevels) # Get the assembler object we just created assembler = problem.getAssembler() # Set the load P = 1.0e3 force = TopOptUtils.computeVertexLoad('pt1', forest, assembler, [0, P, 0]) temp = TopOptUtils.computeVertexLoad('pt2', forest, assembler, [0, 0, P]) force.axpy(1.0, temp) # Set the load cases into the topology optimization problem problem.setLoadCases([force]) # Compute the fixed mass target lx = 50.0 # mm ly = 10.0 # mm lz = 10.0 # mm vol = lx*ly*lz vol_frac = 0.25 density = 2600.0 m_fixed = vol_frac*(vol*density) # Set the mass constraint funcs = [functions.StructuralMass(assembler)] problem.addConstraints(0, funcs, [-m_fixed], [-1.0/m_fixed]) # Set the objective (scale the compliance objective) problem.setObjective([1.0e3]) # Initialize the problem and set the prefix problem.initialize() return problem
def get_functions(self, scenario, bodies): if self.tacs_proc: feval = self.integrator[scenario.id].evalFunctions(self.funclist) for i, func in enumerate(scenario.functions): if func.analysis_type == 'structural' and func.adjoint: func.value = feval[i] if func.analysis_type == 'structural' and func.name == 'mass': funclist = [functions.StructuralMass(self.tacs)] value = self.tacs.evalFunctions(funclist) func.value = value[0] for func in scenario.functions: func.value = self.comm.bcast(func.value, root=0) return functions
def eval_gradients(self, scenario): """ Evaluate gradients with respect to structural design variables""" if self.tacs_proc: self.func_grad = [] for func in range(len(self.funclist)): dfdx = self.integrator[scenario.id].getGradient(func, ) dfdx_arr = dfdx.getArray() self.func_grad.append(dfdx_arr.copy()) #self.func_grad.append(dfdx_arr[func*self.ndvs:(func+1)*self.ndvs].copy()) for func in scenario.functions: if func.analysis_type == 'structural' and func.name == 'mass': funclist = [functions.StructuralMass(self.assembler)] dvsens = np.zeros(self.num_components) self.assembler.addDVSens([funclist[0]], [dvsens]) self.func_grad.append(dvsens.copy()) return
def __init__(self, tacs, f5): # Set the communicator pointer self.comm = MPI.COMM_WORLD self.rank = self.comm.Get_rank() self.size = self.comm.Get_size() self.nvars = num_components / self.size if num_components % self.size != 0 and self.rank == self.size - 1: self.nvars += num_components % self.size self.ncon = 1 # Initialize the base class super(CRMSizing, self).__init__(self.comm, self.nvars, self.ncon) self.tacs = tacs self.f5 = f5 self.res = self.tacs.createVec() self.ans = self.tacs.createVec() self.mat = self.tacs.createFEMat() self.pc = TACS.Pc(self.mat) # Create list of required TACS functions (mass, ksfailure) self.mass = functions.StructuralMass(self.tacs) ksweight = 50.0 alpha = 1.0 self.ksfailure = functions.KSFailure(self.tacs, ksweight, alpha) self.ksfailure.setLoadFactor(1.5) self.funclist = [self.mass, self.ksfailure] self.svsens = self.tacs.createVec() self.adj = self.tacs.createVec() self.adjSensProdArray = np.zeros(num_components) self.tempdvsens = np.zeros(num_components) # Get initial mass for scaling self.initmass = self.tacs.evalFunctions([self.funclist[0]]) self.xscale = 0.0025 # Keep track of the number of gradient evaluations self.gevals = 0 return
def eval_gradients(self, scenario): """ Evaluate gradients with respect to structural design variables""" if self.tacs_proc: dfdx = np.zeros(len(self.funclist) * self.ndvs, dtype=TACS.dtype) self.integrator[scenario.id].getGradient(dfdx) self.func_grad = [] for func in range(len(self.funclist)): self.func_grad.append(dfdx[func * self.ndvs:(func + 1) * self.ndvs].copy()) for func in scenario.functions: if func.analysis_type == 'structural' and func.name == 'mass': funclist = [functions.StructuralMass(self.tacs)] dvsens = np.zeros(self.num_components) self.tacs.evalDVSens(funclist[0], dvsens) self.func_grad.append(dvsens.copy()) return
def setAssembler(self, assembler, ksfunc): # Create tacs assembler object from mesh loader self.assembler = assembler # Create the list of functions self.funcs = [functions.StructuralMass(self.assembler), ksfunc] # Set up the solver self.ans = self.assembler.createVec() self.res = self.assembler.createVec() self.adjoint = self.assembler.createVec() self.dfdu = self.assembler.createVec() self.mat = self.assembler.createFEMat() self.pc = TACS.Pc(self.mat) self.gmres = TACS.KSM(self.mat, self.pc, 10) # For visualization flag = (TACS.ToFH5.NODES | TACS.ToFH5.DISPLACEMENTS | TACS.ToFH5.STRAINS | TACS.ToFH5.EXTRAS) self.f5 = TACS.ToFH5(self.assembler, TACS.PY_PLANE_STRESS, flag) return
def __init__(self, comm, bdf_name): self.comm = comm struct_mesh = TACS.MeshLoader(self.comm) struct_mesh.scanBDFFile(bdf_name) # Set constitutive properties rho = 2500.0 # density, kg/m^3 E = 70e9 # elastic modulus, Pa nu = 0.3 # poisson's ratio kcorr = 5.0 / 6.0 # shear correction factor ys = 350e6 # yield stress, Pa min_thickness = 0.002 max_thickness = 0.20 thickness = 0.02 # Loop over components, creating stiffness and element object for each num_components = struct_mesh.getNumComponents() for i in range(num_components): descriptor = struct_mesh.getElementDescript(i) # Set the design variable index design_variable_index = i stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, design_variable_index, min_thickness, max_thickness) element = None # Create the element object if descriptor in ["CQUAD", "CQUADR", "CQUAD4"]: element = elements.MITCShell(2, stiff, component_num=i) struct_mesh.setElement(i, element) # Create tacs assembler object from mesh loader self.assembler = struct_mesh.createTACS(6) # Create the KS Function ksWeight = 50.0 self.funcs = [ functions.StructuralMass(self.assembler), functions.KSFailure(self.assembler, ksWeight) ] # Create the forces self.forces = self.assembler.createVec() force_array = self.forces.getArray() force_array[2::6] += 100.0 # uniform load in z direction self.assembler.applyBCs(self.forces) # Set up the solver self.ans = self.assembler.createVec() self.res = self.assembler.createVec() self.adjoint = self.assembler.createVec() self.dfdu = self.assembler.createVec() self.mat = self.assembler.createFEMat() self.pc = TACS.Pc(self.mat) subspace = 100 restarts = 2 self.gmres = TACS.KSM(self.mat, self.pc, subspace, restarts) # Scale the mass objective so that it is O(10) self.mass_scale = 1e-3 # Scale the thickness variables so that they are measured in # mm rather than meters self.thickness_scale = 1000.0 # The number of thickness variables in the problem self.nvars = num_components # The number of constraints (1 global stress constraint that # will use the KS function) self.ncon = 1 # Initialize the base class - this will run the same problem # on all processors super(uCRM_VonMisesMassMin, self).__init__(MPI.COMM_SELF, self.nvars, self.ncon) # Set the inequality options for this problem in ParOpt: # The dense constraints are inequalities c(x) >= 0 and # use both the upper/lower bounds self.setInequalityOptions(dense_ineq=True, use_lower=True, use_upper=True) # For visualization flag = (TACS.ToFH5.NODES | TACS.ToFH5.DISPLACEMENTS | TACS.ToFH5.STRAINS | TACS.ToFH5.EXTRAS) self.f5 = TACS.ToFH5(self.assembler, TACS.PY_SHELL, flag) self.iter_count = 0 return
obj_array = [1.0e2] for ite in range(max_iterations): # Create the TACSAssembler and TMRTopoProblem instance nlevs = mg_levels[ite] assembler, problem, filtr, varmap = createTopoProblem(forest, nlevels=nlevs) # Write out just the mesh - for visualization flag = (TACS.ToFH5.NODES) f5 = TACS.ToFH5(assembler, TACS.PY_PLANE_STRESS, flag) f5.writeToFile(os.path.join(args.prefix, 'plate_mesh%d.f5' % (ite))) # Set the constraint type funcs = [functions.StructuralMass(assembler)] # Add the point loads to the vertices force1 = addVertexLoad(comm, order, forest, 'pt1', assembler, [0.0, -1000.]) force1.scale(-1.0) # Set the load cases forces = [force1] problem.setLoadCases(forces) # Set the mass constraint problem.addConstraints(0, funcs, [-m_fixed], [-1.0 / m_fixed]) problem.setObjective(obj_array) # Initialize the problem and set the prefix problem.initialize()
def create_problem(forest, bcs, props, nlevels): """ Create the TMRTopoProblem object and set up the topology optimization problem. This code is given the forest, boundary conditions, material properties and the number of multigrid levels. Based on this info, it creates the TMRTopoProblem and sets up the mass-constrained compliance minimization problem. Before the problem class is returned it is initialized so that it can be used for optimization. Args: forest (OctForest): Forest object bcs (BoundaryConditions): Boundary condition object props (StiffnessProperties): Material properties object nlevels (int): number of multigrid levels Returns: TopoProblem: Topology optimization problem instance """ # Allocate the creator callback function obj = CreatorCallback(bcs, props) # Create a conforming filter filter_type = 'conform' # Create the problem and filter object problem = TopOptUtils.createTopoProblem(forest, obj.creator_callback, filter_type, nlevels=nlevels, lowest_order=3, design_vars_per_node=design_vars_per_node) # Get the assembler object we just created assembler = problem.getAssembler() # Set the constraint type funcs = [functions.StructuralMass(assembler)] # Create the traction objects that will be used later.. T = 2.5e6 tx = np.zeros(args.order) ty = T*np.ones(args.order) thermal_tractions = [] for findex in range(4): thermal_tractions.append(elements.PSThermoQuadTraction(findex, tx, ty)) # Allocate a thermal traction boundary condition force1 = TopOptUtils.computeTractionLoad('traction', forest, assembler, thermal_tractions) # Set the load cases problem.setLoadCases([force1]) # Set the mass constraint # (m_fixed - m(x))/m_fixed >= 0.0 problem.addConstraints(0, funcs, [-m_fixed], [-1.0/m_fixed]) problem.setObjective(obj_array, [functions.Compliance(assembler)]) # Initialize the problem and set the prefix problem.initialize() # Set the output file name flag = (TACS.ToFH5.NODES | TACS.ToFH5.DISPLACEMENTS | TACS.ToFH5.EXTRAS) problem.setF5OutputFlags(5, TACS.PY_PLANE_STRESS, flag) return problem
descriptor = mesh.getElementDescript(i) stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, min_thickness, max_thickness) element = None if descriptor in ["CQUAD"]: element = elements.MITC(stiff, gravity, v0, w0) mesh.setElement(i, element) tacs = mesh.createTACS(8) #---------------------------------------------------------------------! # Create the function list for adjoint solve #---------------------------------------------------------------------! funcs = [] funcs.append(functions.StructuralMass(tacs)) funcs.append(functions.Compliance(tacs)) funcs.append(functions.InducedFailure(tacs, 20.0)) funcs.append(functions.KSFailure(tacs, 100.0)) #---------------------------------------------------------------------# # Setup space for function values and their gradients #---------------------------------------------------------------------# num_funcs = len(funcs) num_design_vars = len(x) fvals = np.zeros(num_funcs) dfdx = np.zeros(num_funcs*num_design_vars)
order = 2 bdf = TACS.BDFIntegrator(assembler, t0, tf, num_steps, order) bdf.setPrintLevel(0) # turn off printing # Integrate governing equations bdf.iterate(0) for step in range(1,num_steps+1): bdf.iterate(step) _, uvec, _, _ = bdf.getStates(num_steps) u = uvec.getArray().copy() bdf.writeRawSolution('spring.dat', 0) # Specify the number of design variables and the function to the integrator # (use Structural Mass as a dummy function) num_dvs = 1 funclist = [functions.StructuralMass(assembler)] bdf.setFunctions(funclist, num_dvs) # Solve the adjoint equations and compute the gradient dfdu_vec = assembler.createVec() for step in range(num_steps, -1, -1): bdf.initAdjoint(step) dfdu = dfdu_vec.getArray() if step == num_steps: dfdu[0] = -1.0 else: dfdu[0] = 0.0 bdf.iterateAdjoint(step, [dfdu_vec]) bdf.postAdjoint(step) dfdx = np.array([0.0], dtype=TACS.dtype)
def __init__(self, comm, props, tacs, const, num_materials, xpts=None, conn=None, m_fixed=1.0, min_mat_fraction=-1.0): ''' Analysis problem ''' # Keep the communicator self.comm = comm # Set the TACS object and the constitutive list self.props = props self.tacs = tacs self.const = const # Set the material information self.num_materials = num_materials self.num_elements = self.tacs.getNumElements() # Keep the pointer to the connectivity/positions self.xpts = xpts self.conn = conn # Set the target fixed mass self.m_fixed = m_fixed # Set a fixed material fraction self.min_mat_fraction = min_mat_fraction # Set the number of constraints self.ncon = 1 if self.min_mat_fraction > 0.0: self.ncon += self.num_materials # Set the number of design variables nwblock = 1 self.num_design_vars = (self.num_materials + 1) * self.num_elements # Initialize the super class super(TACSAnalysis, self).__init__(comm, self.num_design_vars, self.ncon, self.num_elements, nwblock) # Set the size of the design variable 'blocks' self.nblock = self.num_materials + 1 # Create the state variable vectors self.res = tacs.createVec() self.u = tacs.createVec() self.psi = tacs.createVec() self.mat = tacs.createFEMat() # Create the preconditioner for the corresponding matrix self.pc = TACS.Pc(self.mat) # Create the KSM object subspace_size = 20 nrestart = 0 self.ksm = TACS.KSM(self.mat, self.pc, subspace_size, nrestart) self.ksm.setTolerances(1e-12, 1e-30) # Set the block size self.nwblock = self.num_materials + 1 # Allocate a vector that stores the gradient of the mass self.gmass = np.zeros(self.num_design_vars) # Create the mass function and evaluate the gradient of the # mass. This is assumed to remain constatnt throughout the # optimization. self.mass_func = functions.StructuralMass(self.tacs) self.tacs.evalDVSens(self.mass_func, self.gmass) # Set the initial variable values self.xinit = np.ones(self.num_design_vars) # Set the initial design variable values xi = self.m_fixed / np.dot(self.gmass, self.xinit) self.xinit[:] = xi # Set the penalization tval = xi * self.num_materials self.xinit[::self.nblock] = tval # Create a temporary vector for the hessian-vector products self.hvec_tmp = np.zeros(self.xinit.shape) # Set the initial linearization/point self.RAMP_penalty = 0.0 self.setNewInitPointPenalty(self.xinit) # Set the number of function/gradient/hessian-vector # evaluations to zero self.fevals = 0 self.gevals = 0 self.hevals = 0 # Evaluate the objective at the initial point self.obj_scale = 1.0 fail, obj, con = self.evalObjCon(self.xinit) self.obj_scale = obj_scale_factor * obj print('objective scaling = ', self.obj_scale) # Create the FH5 file object flag = (TACS.ToFH5.NODES | TACS.ToFH5.DISPLACEMENTS | TACS.ToFH5.STRAINS) self.f5 = TACS.ToFH5(self.tacs, TACS.PY_PLANE_STRESS, flag) return
def create_problem(forest, bcs, props, nlevels): """ Create a TopoProblem instance for mass and frequency constrained compliance minimization. This problem takes in a forest at the current refinement level, boundary condition information storing the names of the geometric entities to apply Dirichlet boundary conditions, the material properties and the number of multigrid levels. Args: forest (OctForest): Forest object bcs (BoundaryConditions): Boundary condition object props (StiffnessProperties): Material properties object nlevels (int): number of multigrid levels Returns: TopoProblem: Topology optimization problem instance """ # Allocate the creator callback function obj = CreatorCallback(bcs, props) # Define the filter type filter_type = 'conform' # Create the problem and filter objects problem = TopOptUtils.createTopoProblem(forest, obj.creator_callback, filter_type, nlevels=nlevels, lowest_order=2) # Get the assembler object we just created assembler = problem.getAssembler() # Set the load P = 1.0e3 force = TopOptUtils.computeVertexLoad('pt1', forest, assembler, [0, P, 0]) temp = TopOptUtils.computeVertexLoad('pt2', forest, assembler, [0, 0, P]) force.axpy(1.0, temp) problem.setLoadCases([force]) # Compute the fixed mass target lx = 50.0 # mm ly = 10.0 # mm lz = 10.0 # mm vol = lx*ly*lz vol_frac = 0.25 density = 2600.0 m_fixed = vol_frac*(vol*density) # Add the fixed mass constraint # (m_fixed - m(x))/m_fixed >= 0.0 funcs = [functions.StructuralMass(assembler)] problem.addConstraints(0, funcs, [-m_fixed], [-1.0/m_fixed]) # Add the natural frequency constraint omega_min = (0.5/np.pi)*(2e4)**0.5 freq_opts = {'use_jd':args.use_jd, 'num_eigs':args.num_eigs, 'num_recycle':args.num_recycle, 'track_eigen_iters':nlevels} TopOptUtils.addNaturalFrequencyConstraint(problem, omega_min, **freq_opts) # Set the compliance objective problem.setObjective(obj_array) # Initialize the problem and set the prefix problem.initialize() return problem