diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 351f7c8e7..cd8d3e2cd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,4 +13,3 @@ python: path: . extra_requirements: - docs - system_packages: true diff --git a/openaerostruct/docs/user_reference/mesh_surface_dict.rst b/openaerostruct/docs/user_reference/mesh_surface_dict.rst index 795ad1e91..baf3a0d21 100644 --- a/openaerostruct/docs/user_reference/mesh_surface_dict.rst +++ b/openaerostruct/docs/user_reference/mesh_surface_dict.rst @@ -87,14 +87,42 @@ The surface dict will be provided to Groups, including ``Geometry``, ``AeroPoint - 3D ndarray - m - ``x, y, z`` coordinates of the mesh vertices, can be created by ``generate_mesh``. + * - span + - 10.0 + - m + - Wing span. + * - taper + - 0.5 + - + - Wing taper ratio. + * - sweep + - 10.0 + - deg + - Wing sweep angle. + * - dihedral + - 5.0 + - deg + - Wing dihedral. * - twist_cp - np.array([0, 5]) - deg - B-spline control points for twist distribution. Array convention is ``[wing tip, ..., root]`` in symmetry cases, and ``[tip, ..., root, ... tip]`` when ``symmetry = False``. * - chord_cp - np.array([0.1, 5]) + - + - B-spline control points for chord distribution. This is a chord scaler applied to the initial mesh, not the chord value [m] itself. Array convention is the same as ``twist_cp``. + * - xshear_cp + - np.array([0.1, 0.2]) + - m + - B-spline control points for the x-wise shear deformation of the wing. + * - yshear_cp + - np.array([0.1, 0.2]) + - m + - B-spline control points for the y-wise shear deformation of the wing. + * - zshear_cp + - np.array([0.1, 0.2]) - m - - B-spline control points for chord distribution. Array convention is the same than ``twist_cp``. + - B-spline control points for the z-wise shear deformation of the wing. * - ref_axis_pos - 0.25 - @@ -240,6 +268,10 @@ The surface dict will be provided to Groups, including ``Geometry``, ``AeroPoint - 0.12 - - Thickness-over-chord ratio of airfoil provided for the wingbox cross-section. + * - strength_factor _for_upper_skin + - 1.0 + - + - A factor to adjust the yield strength of the upper skin relative to the lower skin. * - data_x_upper - 1D ndarray - @@ -257,5 +289,21 @@ The surface dict will be provided to Groups, including ``Geometry``, ``AeroPoint - - ``y`` coordinates of the wingbox cross-section's lower surface +.. list-table:: FFD parameters + :widths: 20 20 5 55 + :header-rows: 1 + + * - Key + - Example value + - Units + - Description + * - mx + - 2 + - + - Number of the FFD control points in the x direction. + * - my + - 2 + - + - Number of the FFD control points in the y direction. .. TODO: list default values (if any), and whethre each key is required or optional. \ No newline at end of file diff --git a/openaerostruct/examples/drag_polar_ground_effect.py b/openaerostruct/examples/drag_polar_ground_effect.py index 2af10d11e..52d2d63a9 100644 --- a/openaerostruct/examples/drag_polar_ground_effect.py +++ b/openaerostruct/examples/drag_polar_ground_effect.py @@ -138,7 +138,7 @@ def compute_drag_polar_ground_effect(Mach, alphas, heights, surfaces, trimmed=Fa "wing_type": "rect", "symmetry": True, "span": 12.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 0.0, } diff --git a/openaerostruct/examples/rectangular_wing/drag_polar.py b/openaerostruct/examples/rectangular_wing/drag_polar.py index a83f664a8..ff2bbe23a 100644 --- a/openaerostruct/examples/rectangular_wing/drag_polar.py +++ b/openaerostruct/examples/rectangular_wing/drag_polar.py @@ -37,7 +37,7 @@ "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } diff --git a/openaerostruct/examples/rectangular_wing/mphys_example.py b/openaerostruct/examples/rectangular_wing/mphys_example.py index 333c1060f..ac5b1ab95 100644 --- a/openaerostruct/examples/rectangular_wing/mphys_example.py +++ b/openaerostruct/examples/rectangular_wing/mphys_example.py @@ -17,7 +17,7 @@ def setup(self): "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } diff --git a/openaerostruct/examples/rectangular_wing/opt_chord.py b/openaerostruct/examples/rectangular_wing/opt_chord.py index f32432e46..da04c8590 100644 --- a/openaerostruct/examples/rectangular_wing/opt_chord.py +++ b/openaerostruct/examples/rectangular_wing/opt_chord.py @@ -39,7 +39,7 @@ "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } @@ -105,8 +105,9 @@ prob.driver.options["debug_print"] = ["nl_cons", "objs", "desvars"] # Setup problem and add design variables, constraint, and objective +# wing.chord_cp is the chord scaling applied to the initial mesh prob.model.add_design_var("alpha", lower=-10.0, upper=15.0) -prob.model.add_design_var("wing.chord_cp", lower=1e-3, upper=5.0) +prob.model.add_design_var("wing.chord_cp", lower=1e-3, upper=5.0, units=None) prob.model.add_constraint(point_name + ".wing_perf.CL", equals=0.5) prob.model.add_constraint(point_name + ".wing.S_ref", equals=10.0) prob.model.add_objective(point_name + ".wing_perf.CD", scaler=1e4) diff --git a/openaerostruct/examples/rectangular_wing/opt_twist.py b/openaerostruct/examples/rectangular_wing/opt_twist.py index d781cef4e..d7dfcfff9 100644 --- a/openaerostruct/examples/rectangular_wing/opt_twist.py +++ b/openaerostruct/examples/rectangular_wing/opt_twist.py @@ -38,7 +38,7 @@ "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } diff --git a/openaerostruct/examples/rectangular_wing/run_rect_wing.py b/openaerostruct/examples/rectangular_wing/run_rect_wing.py index 9f21a9025..906f07de6 100644 --- a/openaerostruct/examples/rectangular_wing/run_rect_wing.py +++ b/openaerostruct/examples/rectangular_wing/run_rect_wing.py @@ -36,7 +36,7 @@ "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } diff --git a/openaerostruct/examples/rectangular_wing/run_roll.py b/openaerostruct/examples/rectangular_wing/run_roll.py index 681db75dd..249cb12d4 100644 --- a/openaerostruct/examples/rectangular_wing/run_roll.py +++ b/openaerostruct/examples/rectangular_wing/run_roll.py @@ -37,7 +37,7 @@ "wing_type": "rect", "symmetry": False, "span": 9.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 1.0, "chord_cos_spacing": 1.0, } diff --git a/openaerostruct/geometry/geometry_group.py b/openaerostruct/geometry/geometry_group.py index 1010fb19d..fab772a16 100644 --- a/openaerostruct/geometry/geometry_group.py +++ b/openaerostruct/geometry/geometry_group.py @@ -1,6 +1,7 @@ import numpy as np import openmdao.api as om +from openaerostruct.utils.check_surface_dict import check_surface_dict_keys class Geometry(om.Group): @@ -23,6 +24,9 @@ def initialize(self): 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 ny = surface["mesh"].shape[1] @@ -91,10 +95,10 @@ def setup(self): promotes_inputs=["chord_cp"], promotes_outputs=["chord"], ) - comp.add_spline(y_cp_name="chord_cp", y_interp_name="chord", y_units="m") + 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="m") + 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"]) diff --git a/openaerostruct/geometry/geometry_mesh.py b/openaerostruct/geometry/geometry_mesh.py index 940861eda..f8ed23ddb 100644 --- a/openaerostruct/geometry/geometry_mesh.py +++ b/openaerostruct/geometry/geometry_mesh.py @@ -37,7 +37,7 @@ class GeometryMesh(om.Group): twist[ny] : numpy array 1-D array of rotation angles for each wing slice in degrees. chord_dist[ny] : numpy array - Chord length for each panel edge. + Spanwise distribution of the chord scaler. taper : float Taper ratio for the wing; 1 is untapered, 0 goes to a point at the tip. diff --git a/openaerostruct/geometry/geometry_mesh_transformations.py b/openaerostruct/geometry/geometry_mesh_transformations.py index 514adebe3..6b2f4066f 100644 --- a/openaerostruct/geometry/geometry_mesh_transformations.py +++ b/openaerostruct/geometry/geometry_mesh_transformations.py @@ -123,7 +123,7 @@ class ScaleX(om.ExplicitComponent): mesh[nx, ny, 3] : numpy array Nodal mesh defining the initial aerodynamic surface. chord[ny] : numpy array - Chord length for each panel edge. + Spanwise distribution of the chord scaler. Returns ------- @@ -147,7 +147,7 @@ def setup(self): mesh_shape = self.options["mesh_shape"] val = self.options["val"] self.ref_axis_pos = self.options["ref_axis_pos"] - self.add_input("chord", units="m", val=val) + self.add_input("chord", units=None, val=val) self.add_input("in_mesh", shape=mesh_shape, units="m") self.add_output("mesh", shape=mesh_shape, units="m") diff --git a/openaerostruct/geometry/tests/test_geometry_mesh_transformations.py b/openaerostruct/geometry/tests/test_geometry_mesh_transformations.py index 392163e4f..ce12b06e5 100644 --- a/openaerostruct/geometry/tests/test_geometry_mesh_transformations.py +++ b/openaerostruct/geometry/tests/test_geometry_mesh_transformations.py @@ -164,6 +164,40 @@ def test_scalex_ref_axis_trailing_edge(self): # If chord_scaling_pos = 1, TE should not move assert_near_equal(mesh[-1, :, :], prob["comp.mesh"][-1, :, :], tolerance=1e-10) + def test_scalex_chord_value(self): + # test actual chord value of a rectangular wing + + mesh_dict = { + "num_y": 5, + "num_x": 3, + "wing_type": "rect", + "symmetry": True, + "span": 2.0, + "root_chord": 0.2, + } + mesh_in = generate_mesh(mesh_dict) + + # initial chord + chord_in = mesh_in[-1, 0, 0] - mesh_in[0, 0, 0] # TE - LE + + prob = om.Problem() + group = prob.model + comp = ScaleX(val=np.ones(3), mesh_shape=mesh_in.shape) + group.add_subsystem("comp", comp) + + prob.setup() + chord_scaling = 1.3 + prob.set_val("comp.chord", val=chord_scaling, units=None) # apply chord scaling factor + prob.set_val("comp.in_mesh", val=mesh_in, units="m") + + prob.run_model() + + # chord after manipulation + mesh_out = prob.get_val("comp.mesh", units="m") + chord_out = mesh_out[-1, 0, 0] - mesh_out[0, 0, 0] # TE - LE + + assert_near_equal(chord_in * chord_scaling, chord_out, tolerance=1e-10) + def test_sweep(self): symmetry = False mesh = get_mesh(symmetry) diff --git a/openaerostruct/geometry/tests/test_vsp_mesh.py b/openaerostruct/geometry/tests/test_vsp_mesh.py index ab4c0e5ea..014bd0f54 100644 --- a/openaerostruct/geometry/tests/test_vsp_mesh.py +++ b/openaerostruct/geometry/tests/test_vsp_mesh.py @@ -25,7 +25,7 @@ def test_full(self): "wing_type": "rect", "symmetry": False, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 0.0, } @@ -44,7 +44,7 @@ def test_symm(self): "wing_type": "rect", "symmetry": True, "span": 10.0, - "chord": 1, + "root_chord": 1, "span_cos_spacing": 0.0, } diff --git a/openaerostruct/geometry/utils.py b/openaerostruct/geometry/utils.py index bb747c859..70a7d552f 100644 --- a/openaerostruct/geometry/utils.py +++ b/openaerostruct/geometry/utils.py @@ -1,5 +1,6 @@ import numpy as np from numpy import cos, sin, tan +import warnings # openvsp python interface try: @@ -94,7 +95,7 @@ def scale_x(mesh, chord_dist): mesh[nx, ny, 3] : numpy array Nodal mesh defining the initial aerodynamic surface. chord_dist[ny] : numpy array - Chord length for each panel edge. + Spanwise distribution of the chord scaler. Returns ------- @@ -636,12 +637,39 @@ def get_default_geo_dict(): def generate_mesh(input_dict): # Get defaults and update surface with the user-provided input surf_dict = get_default_geo_dict() + + # Warn if a user provided a key that is not implemented + user_defined_keys = input_dict.keys() + for key in user_defined_keys: + if key not in surf_dict: + warnings.warn( + "Key `{}` in mesh_dict is not implemented and will be ignored".format(key), + category=RuntimeWarning, + stacklevel=2, + ) + # Warn if a user did not define important keys + for key in ["num_x", "num_y", "wing_type", "symmetry"]: + if key not in user_defined_keys: + warnings.warn( + "Missing `{}` in mesh_dict. The default value of {} will be used.".format(key, surf_dict[key]), + category=RuntimeWarning, + stacklevel=2, + ) + + # Apply user-defined options surf_dict.update(input_dict) + # Warn if a user defined span and root_chord for CRM + if surf_dict["wing_type"] == "CRM": + if "span" in user_defined_keys or "root_chord" in user_defined_keys: + warnings.warn( + "`span` and `root_chord` in mesh_dict will be ignored for the CRM wing.", + category=RuntimeWarning, + stacklevel=2, + ) + num_x = surf_dict["num_x"] num_y = surf_dict["num_y"] - span = surf_dict["span"] - chord = surf_dict["root_chord"] span_cos_spacing = surf_dict["span_cos_spacing"] chord_cos_spacing = surf_dict["chord_cos_spacing"] @@ -655,6 +683,8 @@ def generate_mesh(input_dict): # Generate rectangular mesh if surf_dict["wing_type"] == "rect": + span = surf_dict["span"] + chord = surf_dict["root_chord"] mesh = gen_rect_mesh(num_x, num_y, span, chord, span_cos_spacing, chord_cos_spacing) # Generate CRM mesh. Note that this outputs twist information diff --git a/openaerostruct/integration/aerostruct_groups.py b/openaerostruct/integration/aerostruct_groups.py index ee97daa81..7eb1278be 100644 --- a/openaerostruct/integration/aerostruct_groups.py +++ b/openaerostruct/integration/aerostruct_groups.py @@ -11,6 +11,7 @@ from openaerostruct.aerodynamics.compressible_states import CompressibleVLMStates from openaerostruct.structures.tube_group import TubeGroup from openaerostruct.structures.wingbox_group import WingboxGroup +from openaerostruct.utils.check_surface_dict import check_surface_dict_keys import openmdao.api as om @@ -26,6 +27,9 @@ def setup(self): DVGeo = self.options["DVGeo"] connect_geom_DVs = self.options["connect_geom_DVs"] + # key validation of the surface dict + check_surface_dict_keys(surface) + geom_promotes_in = [] geom_promotes_out = ["mesh"] diff --git a/openaerostruct/tests/test_aero_atmos.py b/openaerostruct/tests/test_aero_atmos.py index f2b88834b..e54e0486f 100644 --- a/openaerostruct/tests/test_aero_atmos.py +++ b/openaerostruct/tests/test_aero_atmos.py @@ -31,8 +31,6 @@ def test(self): "fem_model_type": "tube", "twist_cp": twist_cp, "mesh": mesh, - "num_x": mesh.shape[0], - "num_y": mesh.shape[1], # Aerodynamic performance of the lifting surface at # an angle of attack of 0 (alpha=0). # These CL0 and CD0 values are added to the CL and CD diff --git a/openaerostruct/tests/test_simple_rect_aero.py b/openaerostruct/tests/test_simple_rect_aero.py index f5843753e..33dc962dc 100644 --- a/openaerostruct/tests/test_simple_rect_aero.py +++ b/openaerostruct/tests/test_simple_rect_aero.py @@ -20,7 +20,6 @@ def test(self): surf_dict = { # Wing definition "name": "wing", # name of the surface - "type": "aero", "symmetry": True, # if true, model one half of wing # reflected across the plane y = 0 "S_ref_type": "wetted", # how we compute the wing area, diff --git a/openaerostruct/tests/test_simple_rect_aero_roll.py b/openaerostruct/tests/test_simple_rect_aero_roll.py index 394656e20..5f4de819b 100644 --- a/openaerostruct/tests/test_simple_rect_aero_roll.py +++ b/openaerostruct/tests/test_simple_rect_aero_roll.py @@ -20,7 +20,6 @@ def test(self): surf_dict = { # Wing definition "name": "wing", # name of the surface - "type": "aero", "symmetry": False, # if true, model one half of wing # reflected across the plane y = 0 "S_ref_type": "wetted", # how we compute the wing area, diff --git a/openaerostruct/tests/test_simple_rect_aero_sideslip.py b/openaerostruct/tests/test_simple_rect_aero_sideslip.py index fe88c2c1e..f93660747 100644 --- a/openaerostruct/tests/test_simple_rect_aero_sideslip.py +++ b/openaerostruct/tests/test_simple_rect_aero_sideslip.py @@ -20,7 +20,6 @@ def test(self): surf_dict = { # Wing definition "name": "wing", # name of the surface - "type": "aero", "symmetry": False, # if true, model one half of wing # reflected across the plane y = 0 "S_ref_type": "wetted", # how we compute the wing area, diff --git a/openaerostruct/tests/test_simple_rect_aero_wo_symmetry.py b/openaerostruct/tests/test_simple_rect_aero_wo_symmetry.py index ef870b626..0794070a8 100644 --- a/openaerostruct/tests/test_simple_rect_aero_wo_symmetry.py +++ b/openaerostruct/tests/test_simple_rect_aero_wo_symmetry.py @@ -20,7 +20,6 @@ def test(self): surf_dict = { # Wing definition "name": "wing", # name of the surface - "type": "aero", "symmetry": False, # if true, model one half of wing # reflected across the plane y = 0 "S_ref_type": "wetted", # how we compute the wing area, diff --git a/openaerostruct/tests/test_simple_rect_right_aero.py b/openaerostruct/tests/test_simple_rect_right_aero.py index 4b6b3c903..634a97e99 100644 --- a/openaerostruct/tests/test_simple_rect_right_aero.py +++ b/openaerostruct/tests/test_simple_rect_right_aero.py @@ -43,7 +43,6 @@ def run_problem(self, mesh): surf_dict = { # Wing definition "name": "wing", # name of the surface - "type": "aero", "symmetry": True, # if true, model one half of wing # reflected across the plane y = 0 "S_ref_type": "wetted", # how we compute the wing area, diff --git a/openaerostruct/utils/check_surface_dict.py b/openaerostruct/utils/check_surface_dict.py new file mode 100755 index 000000000..17c3a10e2 --- /dev/null +++ b/openaerostruct/utils/check_surface_dict.py @@ -0,0 +1,78 @@ +import warnings + + +def check_surface_dict_keys(surface): + """ + Key valication function for the OAS surface dict. + Shows a warning if a user provided a key that is (likely) not implemented in OAS. + + Parameters + ---------- + surface : dict + User-defined surface dict + """ + + # NOTE: make sure this is consistent to the documentation's surface dict page + keys_implemented = [ + # wing definition + "name", + "symmetry", + "S_ref_type", + "mesh", + "span", + "taper", + "sweep", + "dihedral", + "twist_cp", + "chord_cp", + "xshear_cp", + "yshear_cp", + "zshear_cp", + "ref_axis_pos", + # aerodynamics + "CL0", + "CD0", + "with_viscous", + "with_wave", + "groundplane", + "k_lam", + "t_over_c_cp", + "c_max_t", + # structure + "fem_model_type", + "E", + "G", + "yield", + "mrho", + "fem_origin", + "wing_weight_ratio", + "exact_failure_constraint", + "struct_weight_relief", + "distributed_fuel_weight", + "fuel_density", + "Wf_reserve", + "n_point_masses", + # tube structure + "thickness_cp", + "radius_cp", + # wingbox structure + "spar_thickness_cp", + "skin_thickness_cp", + "original_wingbox_airfoil_t_over_c", + "strength_factor_for_upper_skin", + "data_x_upper", + "data_y_upper", + "data_x_lower", + "data_y_lower", + # FFD + "mx", + "my", + ] + + for key in surface.keys(): + if key not in keys_implemented: + warnings.warn( + "Key `{}` in surface dict is (likely) not supported in OAS and will be ignored".format(key), + category=RuntimeWarning, + stacklevel=2, + )