How To: LevelOpt¶
LevelOpt is another
ScenarioDescriptor(LevelOptScenarioDescriptor) for level set structural topology optimization. It supports all of the standard linear elastic boundary conditions and a variety ofmetadataoptimization parameters.LevelOpt scenario is created using
LevelOptScenarioDescriptor. It shares the following inputs with otherScenarioDescriptorclasses:boundary_conditions. LevelOpt supports multiple load cases, which can be input to a single optimization scenario using uniqueload_case_id. This allows LevelOpt to consider each load individually rather than the net load.# multiple load case optimization needs unique load_case_ids auto fixed_boundary1 = std::make_shared<Intact::FixedBoundaryDescriptor>(); fixed_boundary1->boundary = Intact::MeshModel("fixed.ply"); fixed_boundary1->load_case_id = 0; auto fixed_boundary2 = std::make_shared<Intact::FixedBoundaryDescriptor>(); fixed_boundary2->boundary = Intact::MeshModel("fixed.ply"); fixed_boundary2->load_case_id = 1; auto load1 = std::make_shared<Intact::VectorForceDescriptor>(); load1->boundary = Intact::MeshModel("load1.ply"); load1->direction = {1, 0, 0}; load1->magnitude = 1000; load1->load_case_id = 0; auto load2 = std::make_shared<Intact::VectorForceDescriptor>(); load2->boundary = Intact::MeshModel("load1.ply"); load2->direction = {1, 0, 0}; load2->magnitude = 1000; load2->load_case_id = 1; auto load_cases = {fixed_boundary1, load1, fixed_boundary2, load2};
internal_conditionsresolutionorcell_sizeunitssolver_typeused in the initial linear elastic simulation with the following options:MKL_PardisoLDLT(default) direct solverAMGCL_amg_rigid_bodyiterative solver
basis_orderused in the initial linear elastic simulation
LevelOpt Metadata Settings¶
The set of
metadataparameters specific to theLevelOptScenarioDescriptorinclude:vol_frac_cons(volume fraction constraint) sets the target volume of the final design as a fraction of the initial volume.voxelSize, or level set cell size, determines the size of the level set grid cells as a fraction of the FEA grid cell size.move_limitcontrols the extent of changes per optimization step, as a factor of thevoxelSize.opt_max_iter(optimization max iterations) sets the maximum number of iterations for the optimization process. Each iteration refines the design by updating the topology based on the objective function and constraints.fix_thicknessspecifies the region around boundary conditions that remains unchanged as a factor of level set grid cell size.smooth_iterdefines the frequency the geometry is smoothed during the optimization process as a number of iterations.enable_fixed_interfacesallows specifying if the interface between the design domain (optimized) and non-design domain should be fully preserved.Truepreserves the interface andFalseallows material to be removed at the interface.num_load_casesis a input required for an optimization scenario with multipleload_case_id. This enables individual load cases to be considered separately during optimization instead of a net load.objective_functionssets the objective function for the optimization. This is a dictionary where the key is the load case ID and the value is the objective function type. Supported types areObjectiveFunctionType::Compliance(default) andObjectiveFunctionType::MaximumStress.Note The
MaximumStressobjective function is currently in a beta stage. Due to the nature of stress optimization, it may not be suitable for all problems. It performs best on problems with clear stress concentrations away from restraints and boundary conditions. Using it in other cases may lead to non-intuitive or unusable designs. For best results with stress optimization, we recommend using higher fidelity models (finercell_sizeorvoxelSize) and a lowermove_limit(less than 1.0, or approximately, 0.1 to 0.5). Further, we recommend increasingfix_thicknessor creating non-design domain regions near boundaries.optimizer_overrideallows overriding the default optimization solver. The default isOptimizerType::Intact. Another option isOptimizerType::NLOpt.weightsallows specifying the weight for each load case in a multi-load case optimization. This is a dictionary where the key is the load case ID and the value is the weight.Note The
weightsmetadata is currently in a beta stage. A 0.5/0.5 weighting between load cases, especially those with differentobjective_functions, may not result in an equal balance. Any weight can be input, and it is useful to query the boundary sensitivities to understand the order of magnitude for each load case’s sensitivity to inform the weighting. Using compliance to stabilize the optimization can also lead to better designs; specifically, using a small compliance weight (e.g., 1e-3 to 1e-5) relative to a 1.0 weight for a stress objective can lead to more stable designs while still minimizing stress.output_directoryis an optional input that specifies an output directory to write per-iteration optimization results. The results that are written per-iteration are the design written as a PLY file, a CSV file namediteration_history.txtthat contains the iteration number, the load case number, the compliance and the volume fraction of the design.
// Setup the LevelOpt optimization scenario descriptor Intact::LevelOptScenarioDescriptor leveloptscenario; // Standard scenario parameters leveloptscenario.materials = {{"Aluminum": material}}; leveloptscenario.metadata.cell_size = 2.5; leveloptscenario.metadata.units = Intact::UnitSystem::MeterKilogramSecond; leveloptscenario.metadata.solver_override = Intact::SolverType::MKL_PardisoLDLT; // direct LinearElastic solver leveloptscenario.metadata.basis_order = 1; // linear order for LinearElastic solver leveloptscenario.boundary_conditions = load_cases; // consist of two sets of load cases // LevelOpt specific metadata leveloptscenario.optimization_metadata.vol_frac_cons = 0.2; // 20% target volume leveloptscenario.optimization_metadata.voxelSize = 0.5; // 0.5 (default) level set cell size factor leveloptscenario.optimization_metadata.move_limit = 1.0; // 1.0 (default) move limit factor leveloptscenario.optimization_metadata.opt_max_iter = 10; // 10 design iterations leveloptscenario.optimization_metadata.fix_thickness = 4; // 4 (default) level set cells which are unchanged near BCs leveloptscenario.optimization_metadata.smooth_iter = 1; // smoothing performed at every 1 iteration leveloptscenario.optimization_metadata.enable_fixed_interfaces = true; // retain full contact between assembly components leveloptscenario.optimization_metadata.num_load_cases = 2; // 2 load cases created leveloptscenario.optimization_metadata.objective_functions = {{0, Intact::ObjectiveFunctionType::MaximumStress}, {1, Intact::ObjectiveFunctionType::Compliance}}; // set objectives for each load case leveloptscenario.optimization_metadata.weights = {{0, 10}, {1, 1}}; // set weights for each load case leveloptscenario.optimization_metadata.optimizer_override = Intact::OptimizerType::NLOpt; // override default optimizer
LevelOpt Optimization and Supported Queries¶
To run an optimization scenario a design domain
Material Domainmust first be defined. For assemblies, additional non-designMaterial Domain(s)need to be defined. Note, the design domainMeshModelrequires a uniqueinstance_id.// make sure to include instance_id for design_geometry/design_domain auto design_geometry = Intact::MeshModel("design_domain.stl"); design_geometry.instance_id = "ex_design_domain"; // can be any unique id/name design_geometry.refine(); // Create material domains and assembly auto design_domain = Intact::MaterialDomain(design_geometry, "Aluminum", leveloptscenario); component1 = Intact::MaterialDomain(body1, "Aluminum", leveloptscenario); component2 = Intact::MaterialDomain(body2, "Aluminum", leveloptscenario); Intact::Assembly assembly = {design_domain, component1, component2};
The
LevelOptoptimization solver then takes the following arguments:design
Material Domainassembly or list of all design and non-design
Material Domain(s)LevelOptScenarioDescriptorwhich describes the optimization scenario parameters and inputs.starting design
MeshModel, or if no starting design is used, anullptrargument.
// Create and run the simulation auto optimizer = Intact::LevelOpt(design_domain, assembly, leveloptscenario, nullptr); optimizer.optimize(); // Or, create and run the simulation with optional starting design auto starting_design = Intact::MeshModel("starting_design.ply"); optimizer = Intact::LevelOpt(design_domain, assembly, leveloptscenario, starting_design); optimizer.optimize();
LevelOpt Queries include support for two
GlobalQueryTypeclasses,ComplianceandVolumeFractionand twoFieldQueryclasses,BoundarySensitivityandBoundaryVelocity. A discrete index argument corresponding to the optimization iteration is required to query for any of the previously described quantities.// LevelOpt global queries Intact::GlobalQuery compliance_query = Intact::GlobalQuery(Intact::GlobalQueryType::Compliance, Intact::DiscreteIndex(iteration)); Intact::GlobalQuery volume_fraction_query = Intact::GlobalQuery(Intact::GlobalQueryType::VolumeFraction, Intact::DiscreteIndex(iteration)); auto compliance = optimizer.sample(compliance_query); auto volume_fraction = optimizer.sample(volume_fraction_query); double compliance_value = compliance.get(0,0); double volume_fraction_value = volume_fraction.get(0,0); // LevelOpt field queries auto boundary_sensitivity_query = Intact::FieldQuery(Intact::Field::BoundarySensitivity, Intact::DiscreteIndex(0)); auto boundary_velocity_query = Intact::FieldQuery(Intact::Field::BoundaryVelocity, Intact::DiscreteIndex(0)); // no QueryResult() object, because boundary sensitivity is only defined on design boundary auto r_s = optimizer.sample(boundary_sensitivity_query); // no QueryResult() object, because boundary velocity is only defined on design boundary auto r_v = optimizer.sample(boundary_velocity_query); double sensitivity_value = r_s.get(0,0) // tuple of dimension 1 for each point double velocity_value = r_v.get(0,0) // tuple of dimension 1 for each point
Output mesh designs can be stored using
getDesigns()from the LevelOpt scenario optimization.
// run LevelOpt Scenario
auto optimizer = LevelOpt(design_domain, assembly, leveloptscenario, nullptr);
optimizer.optimize();
// get the design iterations from the optimization scenario
auto designs = optimizer.getDesigns();