Source code for openaerostruct.geometry.geometry_group

import numpy as np

import openmdao.api as om
from openaerostruct.utils.check_surface_dict import check_surface_dict_keys
import openaerostruct.geometry.geometry_mesh_gen as meshGen
from openaerostruct.geometry.geometry_unification import GeomMultiUnification
from openaerostruct.geometry.geometry_multi_join import GeomMultiJoin
from openaerostruct.utils.interpolation import get_normalized_span_coords


[docs] class Geometry(om.Group): """ Group that contains all components needed for any type of OAS problem. Because we use this general group, there's some logic to figure out which components to add and which connections to make. This is especially true for all of the geometric manipulation types, such as twist, sweep, etc., in that we handle the creation of these parameters differently if the user wants to have them vary in the optimization problem. """ def initialize(self): self.options.declare("surface", types=dict) self.options.declare("DVGeo", default=None) self.options.declare("connect_geom_DVs", default=True) # The option "connect_geom_DVs" is no longer necessary, but we still keep it to be backward compatible. def setup(self): surface = self.options["surface"] # key validation of the surface dict check_surface_dict_keys(surface) # Get the surface name and create a group to contain components # only for this surface if self.options["DVGeo"]: from openaerostruct.geometry.ffd_component import GeometryMesh self.set_input_defaults("shape", val=np.zeros((surface["mx"], surface["my"])), units="m") if "t_over_c_cp" in surface.keys(): n_cp = len(surface["t_over_c_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface, mid_panel=True) comp = self.add_subsystem( "t_over_c_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["t_over_c_cp"], promotes_outputs=["t_over_c"], ) comp.add_spline(y_cp_name="t_over_c_cp", y_interp_name="t_over_c") if surface.get("t_over_c_cp_dv", True): self.set_input_defaults("t_over_c_cp", val=surface["t_over_c_cp"]) self.add_subsystem( "mesh", GeometryMesh(surface=surface, DVGeo=self.options["DVGeo"]), promotes_inputs=["shape"], promotes_outputs=["mesh"], ) else: from openaerostruct.geometry.geometry_mesh import GeometryMesh bsp_inputs = [] if "twist_cp" in surface.keys(): n_cp = len(surface["twist_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface) comp = self.add_subsystem( "twist_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["twist_cp"], promotes_outputs=["twist"], ) comp.add_spline(y_cp_name="twist_cp", y_interp_name="twist", y_units="deg") bsp_inputs.append("twist") # Since default assumption is that we want tail rotation as a design variable, add this to allow for trimmed drag polar where the tail rotation should not be a design variable if surface.get("twist_cp_dv", True): self.set_input_defaults("twist_cp", val=surface["twist_cp"], units="deg") if "chord_cp" in surface.keys(): n_cp = len(surface["chord_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface) comp = self.add_subsystem( "chord_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["chord_cp"], promotes_outputs=["chord"], ) comp.add_spline(y_cp_name="chord_cp", y_interp_name="chord", y_units=None) bsp_inputs.append("chord") if surface.get("chord_cp_dv", True): self.set_input_defaults("chord_cp", val=surface["chord_cp"], units=None) if "t_over_c_cp" in surface.keys(): n_cp = len(surface["t_over_c_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface, mid_panel=True) comp = self.add_subsystem( "t_over_c_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["t_over_c_cp"], promotes_outputs=["t_over_c"], ) comp.add_spline(y_cp_name="t_over_c_cp", y_interp_name="t_over_c") if surface.get("t_over_c_cp_dv", True): self.set_input_defaults("t_over_c_cp", val=surface["t_over_c_cp"]) if "xshear_cp" in surface.keys(): n_cp = len(surface["xshear_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface) comp = self.add_subsystem( "xshear_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["xshear_cp"], promotes_outputs=["xshear"], ) comp.add_spline(y_cp_name="xshear_cp", y_interp_name="xshear", y_units="m") bsp_inputs.append("xshear") if surface.get("xshear_cp_dv", True): self.set_input_defaults("xshear_cp", val=surface["xshear_cp"], units="m") if "yshear_cp" in surface.keys(): n_cp = len(surface["yshear_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface) comp = self.add_subsystem( "yshear_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["yshear_cp"], promotes_outputs=["yshear"], ) comp.add_spline(y_cp_name="yshear_cp", y_interp_name="yshear", y_units="m") bsp_inputs.append("yshear") if surface.get("yshear_cp_dv", True): self.set_input_defaults("yshear_cp", val=surface["yshear_cp"], units="m") if "zshear_cp" in surface.keys(): n_cp = len(surface["zshear_cp"]) # Add bspline components for active bspline geometric variables. x_interp = get_normalized_span_coords(surface) comp = self.add_subsystem( "zshear_bsp", om.SplineComp( method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)} ), promotes_inputs=["zshear_cp"], promotes_outputs=["zshear"], ) comp.add_spline(y_cp_name="zshear_cp", y_interp_name="zshear", y_units="m") bsp_inputs.append("zshear") if surface.get("zshear_cp_dv", True): self.set_input_defaults("zshear_cp", val=surface["zshear_cp"], units="m") if "sweep" in surface.keys(): bsp_inputs.append("sweep") if surface.get("sweep_dv", True): self.set_input_defaults("sweep", val=surface["sweep"], units="deg") if "span" in surface.keys(): bsp_inputs.append("span") if surface.get("span_dv", True): self.set_input_defaults("span", val=surface["span"], units="m") if "dihedral" in surface.keys(): bsp_inputs.append("dihedral") if surface.get("dihedral_dv", True): self.set_input_defaults("dihedral", val=surface["dihedral"], units="deg") if "taper" in surface.keys(): bsp_inputs.append("taper") if surface.get("taper_dv", True): self.set_input_defaults("taper", val=surface["taper"]) self.add_subsystem( "mesh", GeometryMesh(surface=surface), promotes_inputs=bsp_inputs, promotes_outputs=["mesh"] )
# Function that constructs the individual section surface data dictionaries
[docs] def build_sections(surface): """This function returns an OpenMDAO Independent Variable Component with an output vector appropriately named and sized to function as an unified B-spline that joins multiple sections by construction. Parameters ---------- surface: dict OpenAeroStruct multi-section surface dictionary Returns ------- section_surfaces : list List of OpenAeroStruct surface dictionaries for each individual surface """ # Get number of sections num_sections = surface["num_sections"] if surface["meshes"] == "gen-meshes": # Verify that all required inputs for automatic mesh generation are provided for each section if len(surface["ny"]) != num_sections: raise ValueError("Number of spanwise points needs to be provided for each section") if len(surface["taper"]) != num_sections: raise ValueError("Taper needs to be provided for each section") if len(surface["span"]) != num_sections: raise ValueError("Span needs to be provided for each section") if len(surface["sweep"]) != num_sections: raise ValueError("Sweep needs to be provided for each section") # Generate unified and individual section meshes mesh, sec_meshes = meshGen.generate_mesh(surface) else: # Allow user to provide mesh for each section if len(surface["meshes"]) != num_sections: raise ValueError("A mesh needs to be provided for each section.") sec_meshes = surface["meshes"] if len(surface["sec_name"]) != num_sections: raise ValueError("A name needs to be provided for each section.") # List of support keys for multi-section wings # NOTE: make sure this is consistent to the documentation's surface dict page target_keys = [ # Essential Info "num_section", "symmetry", "S_ref_type", "ref_axis_pos", # wing definition "span", "taper", "sweep", "dihedral", "twist_cp", "chord_cp", "xshear_cp", "yshear_cp", "zshear_cp", # aerodynamics "CL0", "CD0", "with_viscous", "with_wave", "groundplane", "k_lam", "t_over_c_cp", "c_max_t", ] # Constructs a list of section dictionaries and adds the specified supported keys and values from the mult-section surface dictionary. surface_sections = [] num_sections = surface["num_sections"] for i in range(num_sections): section = {} for k in set(surface).intersection(target_keys): if type(surface[k]) is list: section[k] = surface[k][i] else: section[k] = surface[k] section["mesh"] = sec_meshes[i] section["name"] = surface["sec_name"][i] surface_sections.append(section) return surface_sections
[docs] class MultiSecGeometry(om.Group): """ Group that contains the section geometery groups for the multi-section surface This group handles the creation of each section geometry group based on parameters supplied in the multi-section surface dictionary. Meshes for each section can be provided by the user or automatically generated based on parameters supplied in the surface dictionary. The group also adds a mesh unification component that combines the individual section for each mesh into a singular unified mesh for use in aero components. Optionally, the joining component can be added that computes the edge distances between sections. This information can be used to set a distance constraint along the specified axes if needed. """ def initialize(self): self.options.declare("surface", types=dict) # Multi-section surface dictionary self.options.declare( "joining_comp", types=bool, default=False ) # Specify if a distance computation component should be added self.options.declare( "dim_constr", types=list, default=[] ) # List of arrays corresponding to each shared edge between section along the surface. Each array inidicates along which axes the distance constarint is applied([x y z]) def setup(self): surface = self.options["surface"] joining_comp = self.options["joining_comp"] dc = self.options["dim_constr"] # key validation of the surface dict check_surface_dict_keys(surface) sec_dicts = build_sections(surface) section_names = [] for sec in sec_dicts: geom_group = Geometry(surface=sec) self.add_subsystem(sec["name"], geom_group) section_names.append(sec["name"]) # Add the mesh unification component unification_name = "{}_unification".format(surface["name"]) uni_mesh = GeomMultiUnification(sections=sec_dicts, surface_name=surface["name"]) self.add_subsystem(unification_name, uni_mesh) # Connect each section mesh to mesh unification component inputs for sec_name in section_names: self.connect("{}.mesh".format(sec_name), "{}.{}_def_mesh".format(unification_name, sec_name)) # Connect each section t over c B-spline to t over c unification component if needed if "t_over_c_cp" in surface.keys(): for sec_name in section_names: self.connect("{}.t_over_c".format(sec_name), "{}.{}_t_over_c".format(unification_name, sec_name)) if joining_comp: # Add section joining component to output edge distances joining_name = "{}_joining".format(surface["name"]) join = GeomMultiJoin(sections=sec_dicts, dim_constr=dc) self.add_subsystem(joining_name, join) for sec_name in section_names: self.connect("{}.mesh".format(sec_name), "{}.{}_join_mesh".format(joining_name, sec_name))