diff --git a/aviary/docs/user_guide_unreviewed/subsystems/blended_wing_body_FLOPS.ipynb b/aviary/docs/user_guide_unreviewed/subsystems/blended_wing_body_FLOPS.ipynb index 270a2aa32f..131845df0e 100644 --- a/aviary/docs/user_guide_unreviewed/subsystems/blended_wing_body_FLOPS.ipynb +++ b/aviary/docs/user_guide_unreviewed/subsystems/blended_wing_body_FLOPS.ipynb @@ -149,9 +149,8 @@ "\n", "glue_variable(get_variable_name(Aircraft.BWB.NUM_BAYS), md_code=True)\n", "glue_variable(get_variable_name(Aircraft.Fuselage.SIMPLE_LAYOUT), md_code=True)\n", - "\n", - "Rear_spar_percent_chord = get_all_non_aviary_names(BWBFuselagePrelim, include_in_out='in')[0]\n", - "glue_variable('Rear_spar_percent_chord', Rear_spar_percent_chord, md_code=True)" + "glue_variable(get_variable_name(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT), md_code=True)\n", + "glue_variable(get_variable_name(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE), md_code=True)" ] }, { @@ -165,7 +164,7 @@ "\n", "![Fuselage size](./images/BWB_FLOPS_Fuselage_Simple_Geom.png)\n", "\n", - "Variable {glue:md}`Rear_spar_percent_chord` serves as the ratio of fuselage length and compartment length. It says that the rear spar is mounted at the side of fuselage which is 70% (default) of fuselage length from the leading edge. {glue:md}`Rear_spar_percent_chord` is hard coded for now, but can be easily converted to an Aviary variable.\n", + "Variable {glue:md}`Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT` serves as the ratio of fuselage length and compartment length at the wing root, while variable {glue:md}`Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE` serves as the ratio of fuselage length and compartment length at the centeline. The default values for each of these is 0.7.\n", "\n", "The detailed layout involves more details like the number of passengers, seat pitch and seat abreast. Fuselage length and width are no longer inputs but outputs. If you have those data, it is a better choice.\n", "\n", @@ -395,7 +394,8 @@ "glue_variable(get_variable_name(Aircraft.Fuselage.CABIN_AREA), md_code=True)\n", "glue_variable(get_variable_name(Aircraft.Fuselage.MAX_WIDTH), md_code=True)\n", "glue_variable(get_variable_name(Aircraft.Fuselage.MAX_HEIGHT), md_code=True)\n", - "glue_variable(get_variable_name(Aircraft.Wing.ROOT_CHORD), md_code=True)" + "glue_variable(get_variable_name(Aircraft.Wing.ROOT_CHORD), md_code=True)\n", + "glue_variable(get_variable_name(Aircraft.BWB.WING_ROOT_INDEX), md_code=True)" ] }, { @@ -419,7 +419,9 @@ "- {glue:md}`Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION`\n", "- {glue:md}`Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION`\n", "\n", - "2. Suppose you have a FLOPS model with detailed wings. For example, you have:\n", + "2. FLOPS allows you to define the BWB detailed wing geometry from either the wing root or from the aircraft centerline. To tell FLOPS where the wing actually starts, the fueslage namelist input `NESOB` should contain the index that corresponds to the location in the detailed geometry arrays that contains the values at the wing root. This parameter is also available in Aviary as {glue:md}`Aircraft.BWB.WING_ROOT_INDEX`, though keep in mind that Aviary indexing starts at 0 instead of 1.\n", + "\n", + "3. When converting a FLOPS model, the Aviary detailed wing definition will look like the FLOPS distribution. This one starts at the centerline:\n", "\n", "```\n", "NETAW = 4,\n", @@ -427,22 +429,45 @@ "CHD = 48.25, 33.20, 14.19, 3.220,\n", "TOC = 0.125, 0.125, 0.076, 0.060,\n", "SWL = 0.0, 0.0, 17.0\n", + "\n", + "&FUSEIN\n", + "NESOB = 2\n", "```\n", "\n", "Note that `SWL` has one entry less than the other variables. This is because it measures the sweep angles between distribution points. The corresponding Aviary inputs should be:\n", "\n", "```\n", - "aircraft:wing:input_station_distribution,0.0,0.0,0.2075,0.6927,1.0,unitless\n", - "aircraft:wing:chord_per_semispan_distribution,-1.0,48.25,33.2,14.19,3.22,unitless\n", - "aircraft:wing:thickness_to_chord_distribution,-1.0,0.125,0.125,0.076,0.06,unitless\n", - "aircraft:wing:load_path_sweep_distribution,0.0,0.0,0.0,17.0,deg\n", + "aircraft:blended_wing_body_design:wing_root_index,1,unitless\n", + "aircraft:wing:input_station_distribution,0.0,0.2075,0.6927,1.0,unitless\n", + "aircraft:wing:chord_per_semispan_distribution,48.25,33.2,14.19,3.22,unitless\n", + "aircraft:wing:thickness_to_chord_distribution,0.125,0.125,0.076,0.06,unitless\n", + "aircraft:wing:load_path_sweep_distribution,0.0,0.0,17.0,deg\n", "```\n", "\n", - "Note that an extra entry is added with value of `0.0` or `-1.0`. These variables will be updated by Aviary when it runs. The reason for an extra entry is because FLOPS variables started the distribution of input points from wing root. For conventional aircraft, it is assumed that it is the same as starting from the centerline. But for BWB aircraft, they are different and we have to add an extra point to count for the segment from centerline to wing root. They are set to `0.0` or `-1.0` in the input file and are updated during the run.\n", + "Likewise, this wing definition starts at the wing root, and the Aviary inputs follow:\n", + "\n", + "```\n", + "NETAW = 4,\n", + "ETAW = .2075, .6927, 1.000,\n", + "CHD = 33.20, 14.19, 3.220,\n", + "TOC = 0.125, 0.076, 0.060,\n", + "SWL = 0.0, 17.0\n", + "\n", + "&FUSEIN\n", + "NESOB = 1\n", + "```\n", + "\n", + "```\n", + "aircraft:blended_wing_body_design:wing_root_index,0,unitless\n", + "aircraft:wing:input_station_distribution,0.2075,0.6927,1.0,unitless\n", + "aircraft:wing:chord_per_semispan_distribution,33.2,14.19,3.22,unitless\n", + "aircraft:wing:thickness_to_chord_distribution,0.125,0.076,0.06,unitless\n", + "aircraft:wing:load_path_sweep_distribution,0.0,17.0,deg\n", + "```\n", "\n", - "3. BWB aircraft has an non-pressurized aftbody in addition to the pressurized centerbody. Its mass {glue:md}`Aircraft.Wing.BWB_AFTBODY_MASS` is added to wing mass {glue:md}`Aircraft.Wing.MASS`.\n", + "4. The BWB aircraft has a non-pressurized aftbody in addition to the pressurized centerbody. Its mass {glue:md}`Aircraft.Wing.BWB_AFTBODY_MASS` is added to wing mass {glue:md}`Aircraft.Wing.MASS`.\n", "\n", - "4. In FLOPS, there are several ways to compute the cabin layout. Aviary implemented two of them. This is set by an Aviary variable {glue:md}`Aircraft.Fuselage.SIMPLE_LAYOUT`.\n", + "5. In FLOPS, there are several ways to compute the cabin layout. Aviary implemented two of them. This is set by an Aviary variable {glue:md}`Aircraft.Fuselage.SIMPLE_LAYOUT`.\n", "\n", "If {glue:md}`Aircraft.Fuselage.SIMPLE_LAYOUT` is true, the user should provide fuselage length as input and Aviary will compute the passenger compartment length in the case of conventional aircraft. In the case of BWB aircraft, the user should provide fuselage length, width, leading edge sweek angle and sidebody thickness to chord ratio. Aviary will conpute the passenger compartment length, wing root chord, cabin area, fuselage height, and number of bays.\n", "\n", @@ -462,7 +487,7 @@ ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -476,7 +501,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide_unreviewed/subsystems/images/BWB_FLOPS_Fuselage_Simple_Geom.png b/aviary/docs/user_guide_unreviewed/subsystems/images/BWB_FLOPS_Fuselage_Simple_Geom.png index b4712f5aa2..ee9ca0cf94 100644 Binary files a/aviary/docs/user_guide_unreviewed/subsystems/images/BWB_FLOPS_Fuselage_Simple_Geom.png and b/aviary/docs/user_guide_unreviewed/subsystems/images/BWB_FLOPS_Fuselage_Simple_Geom.png differ diff --git a/aviary/models/aircraft/blended_wing_body/bwb300_baseline_FLOPS.csv b/aviary/models/aircraft/blended_wing_body/bwb300_baseline_FLOPS.csv index b1cf3fec01..eca36bf7c9 100644 --- a/aviary/models/aircraft/blended_wing_body/bwb300_baseline_FLOPS.csv +++ b/aviary/models/aircraft/blended_wing_body/bwb300_baseline_FLOPS.csv @@ -88,6 +88,7 @@ aircraft:fuel:wing_reference_capacity,0.0,lbm aircraft:fuel:wing_reference_capacity_term_a,0.0,unitless aircraft:fuel:wing_reference_capacity_term_b,0.0,unitless aircraft:furnishings:mass_scaler,1.118,unitless +aircraft:fuselage:height_to_width_ratio,0.1792,unitless aircraft:fuselage:sidebody_thickness_to_chord,0.1792,unitless aircraft:fuselage:laminar_flow_lower,0.0,unitless aircraft:fuselage:laminar_flow_upper,0.0,unitless @@ -142,17 +143,17 @@ aircraft:wing:airfoil_technology,2.0,unitless # aircraft:wing:aspect_ratio,5.4252,unitless # aircraft:wing:aspect_ratio_reference,0.0,unitless aircraft:wing:bending_material_mass_scaler,1.0,unitless -aircraft:wing:chord_per_semispan_distribution,-1.0,48.25,33.2,18.97,14.19,10.2,3.22,unitless +aircraft:wing:chord_per_semispan_distribution,48.25,33.2,18.97,14.19,10.2,3.22,unitless aircraft:wing:composite_fraction,0.85,unitless aircraft:wing:control_surface_area_ratio,0.3,unitless aircraft:wing:detailed_wing,True,unitless aircraft:wing:dihedral,3.0,deg aircraft:wing:glove_and_bat,1230.5,ft**2 -aircraft:wing:input_station_distribution,0.0,0.0,0.2075,0.415,0.6927,0.928,1.0,unitless +aircraft:wing:input_station_distribution,0.0,0.2075,0.415,0.6927,0.928,1.0,unitless aircraft:wing:laminar_flow_lower,0.0,unitless aircraft:wing:laminar_flow_upper,0.0,unitless aircraft:wing:load_distribution_control,2.0,unitless -aircraft:wing:load_path_sweep_distribution,0.0,0.0,0.0,17.0,17.0,17.0,deg +aircraft:wing:load_path_sweep_distribution,0.0,0.0,17.0,17.0,17.0,deg aircraft:wing:mass_scaler,1.0,unitless aircraft:wing:max_camber_at_70_semispan,2.0,unitless aircraft:wing:misc_mass_scaler,1.0,unitless @@ -168,7 +169,7 @@ aircraft:wing:surface_control_mass_scaler,1.0,unitless aircraft:wing:sweep,35.7,deg aircraft:wing:taper_ratio,0.311,unitless aircraft:wing:thickness_to_chord,0.11,unitless -aircraft:wing:thickness_to_chord_distribution,-1.0,0.125,0.125,0.076,0.076,0.076,0.06,unitless +aircraft:wing:thickness_to_chord_distribution,0.125,0.125,0.076,0.076,0.076,0.06,unitless aircraft:wing:thickness_to_chord_reference,0.0,unitless aircraft:wing:ultimate_load_factor,3.75,unitless aircraft:wing:var_sweep_mass_penalty,0.0,unitless @@ -255,7 +256,6 @@ CONFIN.WSR,0.0 ENGDIN.IFILL,2 ENGDIN.MAXCR,1 ENGDIN.NGPRT,1 -FUSEIN.TCF,0.1792 FUSEIN.XLW,50.0 MISSIN.ACTAB,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 MISSIN.ADTAB,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv b/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv index 27bda7f44d..d699545e35 100644 --- a/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv +++ b/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv @@ -90,6 +90,7 @@ aircraft:fuel:wing_fuel_fraction,0.68835495693,unitless aircraft:fuel:unusable_fuel_mass_scaler,1.0,unitless aircraft:furnishings:mass_scaler,1.0,unitless aircraft:fuselage:sidebody_thickness_to_chord,0.11,unitless +aircraft:fuselage:height_to_width_ratio,0.11,unitless aircraft:fuselage:mass_scaler,1.0,unitless aircraft:fuselage:military_cargo_floor,False,unitless aircraft:fuselage:num_fuselages,1,unitless @@ -127,14 +128,14 @@ aircraft:vertical_tail:wetted_area_scaler,1.0,unitless aircraft:wing:aeroelastic_tailoring_factor,0.0,unitless aircraft:wing:airfoil_technology,2.0,unitless aircraft:wing:bending_material_mass_scaler,1.0,unitless -aircraft:wing:chord_per_semispan_distribution,-1.0,58.03,0.4491,0.3884,0.3317,0.2886,0.2537,0.2269,0.2121,0.1983,0.1843,0.1704,0.1565,0.1426,0.1287,unitless +aircraft:wing:chord_per_semispan_distribution,58.03,0.4491,0.3884,0.3317,0.2886,0.2537,0.2269,0.2121,0.1983,0.1843,0.1704,0.1565,0.1426,0.1287,unitless aircraft:wing:composite_fraction,1,unitless aircraft:wing:control_surface_area_ratio,0.333,unitless aircraft:wing:detailed_wing,True,unitless aircraft:wing:dihedral,3.0,deg aircraft:wing:glove_and_bat,121.05,ft**2 -aircraft:wing:input_station_distribution,0.0,0.35,0.4,0.45,0.5,0.55,0.6,0.6499,0.7,0.75,0.8,0.85,0.8999,0.95,1,unitless -aircraft:wing:load_path_sweep_distribution,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,42.9,42.9,42.9,42.9,42.9,42.9,deg +aircraft:wing:input_station_distribution,0.35,0.4,0.45,0.5,0.55,0.6,0.6499,0.7,0.75,0.8,0.85,0.8999,0.95,1,unitless +aircraft:wing:load_path_sweep_distribution,0.0,0.0,0.0,0.0,0.0,0.0,0,42.9,42.9,42.9,42.9,42.9,42.9,deg aircraft:wing:load_distribution_control,2.0,unitless aircraft:wing:mass_scaler,1,unitless aircraft:wing:max_camber_at_70_semispan,2,unitless @@ -151,7 +152,7 @@ aircraft:wing:sweep,35.7,deg aircraft:wing:taper_ratio,0.311,unitless aircraft:wing:thickness_to_chord,0.11,unitless aircraft:wing:thickness_to_chord_reference,0.11,unitless -aircraft:wing:thickness_to_chord_distribution,-1.0,0.15,0.1132,0.0928,0.0822,0.0764,0.0742,0.0746,0.0758,0.0758,0.0756,0.0756,0.0758,0.076,0.076,unitless +aircraft:wing:thickness_to_chord_distribution,0.15,0.1132,0.0928,0.0822,0.0764,0.0742,0.0746,0.0758,0.0758,0.0756,0.0756,0.0758,0.076,0.076,unitless aircraft:wing:ultimate_load_factor,3.75,unitless aircraft:wing:var_sweep_mass_penalty,0.0,unitless aircraft:wing:wetted_area_scaler,1.0,unitless diff --git a/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv b/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv index d19cf167ee..4a4468839d 100644 --- a/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv +++ b/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv @@ -7,6 +7,7 @@ aircraft:anti_icing:mass_scaler,1.0,unitless aircraft:apu:mass_scaler,1.0,unitless aircraft:avionics:mass_scaler,1.0,unitless aircraft:blended_wing_body_design:detailed_wing_provided,False,unitless +aircraft:blended_wing_body_design:max_bay_width,12.0,ft aircraft:blended_wing_body_design:passenger_leading_edge_sweep,45,deg aircraft:canard:area,0.0,ft**2 aircraft:canard:aspect_ratio,0.0,unitless @@ -135,7 +136,7 @@ aircraft:wing:control_surface_area_ratio,0.333,unitless aircraft:wing:detailed_wing,True,unitless aircraft:wing:dihedral,3.0,deg aircraft:wing:glove_and_bat,121.05,ft**2 -aircraft:wing:input_station_distribution,0.0,0.5,1.0,unitless +aircraft:wing:input_station_distribution,0.5,1.0,unitless aircraft:wing:load_distribution_control,2.0,unitless aircraft:wing:mass_scaler,1,unitless aircraft:wing:max_camber_at_70_semispan,2,unitless diff --git a/aviary/subsystems/aerodynamics/aerodynamics_builder.py b/aviary/subsystems/aerodynamics/aerodynamics_builder.py index 5aca50fa4f..c233be8948 100644 --- a/aviary/subsystems/aerodynamics/aerodynamics_builder.py +++ b/aviary/subsystems/aerodynamics/aerodynamics_builder.py @@ -713,10 +713,22 @@ def get_parameters(self, aviary_inputs=None, user_options=None, subsystem_option def get_timeseries(self, aviary_inputs=None, user_options=None, subsystem_options=None): """Call get_timeseries() on all engine models and return combined result.""" - timeseries_vars = [ - Dynamic.Vehicle.DRAG_COEFFICIENT, - Dynamic.Vehicle.LIFT_COEFFICIENT, - ] + if subsystem_options is None: + subsystem_options = {} + + timeseries_vars = [] + + try: + method = subsystem_options['method'] + except KeyError: + method = None + + if method != 'external': + timeseries_vars = [ + Dynamic.Vehicle.DRAG_COEFFICIENT, + Dynamic.Vehicle.LIFT_COEFFICIENT, + ] + return timeseries_vars def get_pre_mission_bus_variables(self, aviary_inputs=None, mission_info=None): diff --git a/aviary/subsystems/geometry/flops_based/fuselage.py b/aviary/subsystems/geometry/flops_based/fuselage.py index 4c49ab40b6..e9847870a6 100644 --- a/aviary/subsystems/geometry/flops_based/fuselage.py +++ b/aviary/subsystems/geometry/flops_based/fuselage.py @@ -67,14 +67,6 @@ def setup(self): add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') add_aviary_input(self, Aircraft.Fuselage.MAX_HEIGHT, units='ft') add_aviary_input(self, Aircraft.Wing.ROOT_CHORD, units='ft') - self.add_input( - 'Rear_spar_percent_chord', - 0.7, - units='unitless', - desc='RSPSOB: Rear spar percent chord for BWB at side of body, ' - ' or more precisely, the passenger compartment ends at the ' - ' 70% of fuselage length from the leading edge.', - ) add_aviary_output(self, Aircraft.Fuselage.REF_DIAMETER, units='ft') add_aviary_output(self, Aircraft.Fuselage.PLANFORM_AREA, units='ft**2') @@ -91,7 +83,6 @@ def setup_partials(self): Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.MAX_WIDTH, Aircraft.Wing.ROOT_CHORD, - 'Rear_spar_percent_chord', ], ) @@ -100,20 +91,14 @@ def compute(self, inputs, outputs): length = inputs[Aircraft.Fuselage.LENGTH] max_height = inputs[Aircraft.Fuselage.MAX_HEIGHT] root_chord = inputs[Aircraft.Wing.ROOT_CHORD] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] if length <= 0.0: raise ValueError( f'Aircraft.Fuselage.LENGTH must be positive, however {length} is provided.' ) - if rear_spar_percent_chord <= 0.0: - raise ValueError( - 'Rear_spar_percent_chord must be positive, ' - f'however {rear_spar_percent_chord} is provided.' - ) ref_diameter = 0.5 * (max_height + max_width) - planform_area = max_width * (length + root_chord / rear_spar_percent_chord) / 2.0 + planform_area = max_width * (length + root_chord) / 2.0 outputs[Aircraft.Fuselage.REF_DIAMETER] = ref_diameter outputs[Aircraft.Fuselage.PLANFORM_AREA] = planform_area @@ -122,18 +107,12 @@ def compute_partials(self, inputs, partials): max_width = inputs[Aircraft.Fuselage.MAX_WIDTH] length = inputs[Aircraft.Fuselage.LENGTH] root_chord = inputs[Aircraft.Wing.ROOT_CHORD] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] partials[Aircraft.Fuselage.PLANFORM_AREA, Aircraft.Fuselage.LENGTH] = max_width / 2.0 partials[Aircraft.Fuselage.PLANFORM_AREA, Aircraft.Fuselage.MAX_WIDTH] = ( - length + root_chord / rear_spar_percent_chord + length + root_chord ) / 2.0 - partials[Aircraft.Fuselage.PLANFORM_AREA, Aircraft.Wing.ROOT_CHORD] = ( - max_width / rear_spar_percent_chord / 2.0 - ) - partials[Aircraft.Fuselage.PLANFORM_AREA, 'Rear_spar_percent_chord'] = ( - -max_width * root_chord / rear_spar_percent_chord**2 / 2.0 - ) + partials[Aircraft.Fuselage.PLANFORM_AREA, Aircraft.Wing.ROOT_CHORD] = max_width / 2.0 class SimpleCabinLayout(om.ExplicitComponent): @@ -485,15 +464,15 @@ class BWBSimpleCabinLayout(om.ExplicitComponent): def initialize(self): add_aviary_option(self, Settings.VERBOSITY) add_aviary_option(self, Aircraft.BWB.MAX_NUM_BAYS) + add_aviary_option(self, Aircraft.BWB.MAX_BAY_WIDTH) def setup(self): add_aviary_input(self, Aircraft.Fuselage.LENGTH, units='ft') add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') add_aviary_input(self, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, units='deg') add_aviary_input(self, Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, units='unitless') - self.add_input( - 'Rear_spar_percent_chord', 0.7, units='unitless', desc='RSPCHD at fuselage centerline' - ) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT) add_aviary_output(self, Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH, units='ft') add_aviary_output(self, Aircraft.Wing.ROOT_CHORD, units='ft') @@ -506,7 +485,7 @@ def setup_partials(self): of=[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH], wrt=[ Aircraft.Fuselage.LENGTH, - 'Rear_spar_percent_chord', + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, ], ) self.declare_partials( @@ -515,7 +494,8 @@ def setup_partials(self): Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.MAX_WIDTH, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, - 'Rear_spar_percent_chord', + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, ], ) self.declare_partials( @@ -524,7 +504,7 @@ def setup_partials(self): Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.MAX_WIDTH, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, - 'Rear_spar_percent_chord', + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, ], ) self.declare_partials( @@ -539,10 +519,13 @@ def compute(self, inputs, outputs): verbosity = self.options[Settings.VERBOSITY] length = inputs[Aircraft.Fuselage.LENGTH] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] + rear_spar_percent_chord_cl = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] + rear_spar_percent_chord_root = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] + max_width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] height_to_width = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD] - bay_width_nom = 12.0 # ft + + bay_width_nom = self.options[Aircraft.BWB.MAX_BAY_WIDTH][0] if length <= 0.0: raise ValueError( @@ -553,53 +536,79 @@ def compute(self, inputs, outputs): f'Aircraft.Fuselage.MAX_HEIGHT must be positive, however {max_width} is provided.' ) - pax_compart_length = rear_spar_percent_chord * length + pax_compart_length = rear_spar_percent_chord_cl * length sweep = inputs[Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP] tan_sweep = np.tan(sweep / 57.296) - root_chord = pax_compart_length - tan_sweep * max_width / 2.0 - area_cabin = (pax_compart_length + root_chord) * max_width / 2.0 + sidewall = pax_compart_length - tan_sweep * max_width / 2.0 + area_cabin = (pax_compart_length + sidewall) * max_width / 2.0 max_height = height_to_width * length # Enforce maximum number of bays num_bays_max = self.options[Aircraft.BWB.MAX_NUM_BAYS] - num_bays = int(0.5 + max_width / bay_width_nom) + + # The original fortran code enforced the maximum number of bays with an additional check + # that is equivalent to "rounding up" because we are using max width here. + # TODO: Much of this will need a re-write if we add the FLOPS capability that lets you + # pick the inputs you want, and it calculates the others. This will involve sorting out + # when "WF" means max width, and when it means computed width. + num_bays = np.ceil(0.5 + max_width.real / bay_width_nom) if num_bays.real > num_bays_max and num_bays_max > 0: num_bays = num_bays_max + outputs[Aircraft.BWB.NUM_BAYS] = smooth_int_tanh(num_bays, mu=20.0) outputs[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH] = pax_compart_length - outputs[Aircraft.Wing.ROOT_CHORD] = root_chord + outputs[Aircraft.Wing.ROOT_CHORD] = sidewall / rear_spar_percent_chord_root outputs[Aircraft.Fuselage.CABIN_AREA] = area_cabin outputs[Aircraft.Fuselage.MAX_HEIGHT] = max_height def compute_partials(self, inputs, J): length = inputs[Aircraft.Fuselage.LENGTH] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] + rear_spar_percent_chord_cl = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] + rear_spar_percent_chord_root = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] max_width = inputs[Aircraft.Fuselage.MAX_WIDTH] sweep = inputs[Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP] - tan_sweep = np.tan(sweep / 57.296) - pax_compart_length = rear_spar_percent_chord * length height_to_width = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD] + tan_sweep = np.tan(sweep / 57.296) + pax_compart_length = rear_spar_percent_chord_cl * length + sidewall = pax_compart_length - tan_sweep * max_width / 2.0 + J[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH, Aircraft.Fuselage.LENGTH] = ( - rear_spar_percent_chord + rear_spar_percent_chord_cl ) - J[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH, 'Rear_spar_percent_chord'] = length + J[ + Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH, + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, + ] = length - J[Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.LENGTH] = rear_spar_percent_chord - J[Aircraft.Wing.ROOT_CHORD, 'Rear_spar_percent_chord'] = length + fact1 = (np.cos(sweep / 57.296)) ** 2 + + J[Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.LENGTH] = ( + rear_spar_percent_chord_cl / rear_spar_percent_chord_root + ) + J[Aircraft.Wing.ROOT_CHORD, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] = ( + length / rear_spar_percent_chord_root + ) J[Aircraft.Wing.ROOT_CHORD, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP] = ( - -max_width / (np.cos(sweep / 57.296)) ** 2 / 57.296 / 2.0 + -max_width / fact1 / 57.296 / 2.0 / rear_spar_percent_chord_root + ) + J[Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.MAX_WIDTH] = ( + -tan_sweep / 2.0 / rear_spar_percent_chord_root + ) + J[Aircraft.Wing.ROOT_CHORD, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] = ( + -sidewall / rear_spar_percent_chord_root**2 ) - J[Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.MAX_WIDTH] = -tan_sweep / 2.0 J[Aircraft.Fuselage.CABIN_AREA, Aircraft.Fuselage.LENGTH] = ( - rear_spar_percent_chord * max_width + rear_spar_percent_chord_cl * max_width + ) + J[Aircraft.Fuselage.CABIN_AREA, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] = ( + length * max_width ) - J[Aircraft.Fuselage.CABIN_AREA, 'Rear_spar_percent_chord'] = length * max_width J[Aircraft.Fuselage.CABIN_AREA, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP] = ( - -(max_width**2) / 4.0 / (np.cos(sweep / 57.296)) ** 2 / 57.296 + -(max_width**2) / 4.0 / fact1 / 57.296 ) J[Aircraft.Fuselage.CABIN_AREA, Aircraft.Fuselage.MAX_WIDTH] = ( pax_compart_length - tan_sweep * max_width / 2.0 @@ -625,13 +634,13 @@ def initialize(self): add_aviary_option(self, Aircraft.CrewPayload.Design.SEAT_PITCH_ECONOMY) add_aviary_option(self, Aircraft.BWB.MAX_NUM_BAYS) add_aviary_option(self, Aircraft.BWB.MAX_BAY_WIDTH) + add_aviary_option(self, Aircraft.Fuselage.CABIN_SIDEWALL_LENGTH_MIN) def setup(self): add_aviary_input(self, Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, units='deg') add_aviary_input(self, Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, units='unitless') - self.add_input( - 'Rear_spar_percent_chord', 0.7, units='unitless', desc='RSPCHD at fuselage centerline' - ) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT) add_aviary_output(self, Aircraft.Fuselage.LENGTH, units='ft') add_aviary_output(self, Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH, units='ft') @@ -646,7 +655,8 @@ def setup_partials(self): self.declare_partials('*', '*', method='cs') def compute(self, inputs, outputs): - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] + rear_spar_percent_chord = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] + rear_spar_percent_chord_root = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] sweep = inputs[Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP] height_to_width = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD] tan_sweep = np.tan(sweep / 57.296) @@ -655,7 +665,8 @@ def compute(self, inputs, outputs): bay_width_max, _ = self.options[Aircraft.BWB.MAX_BAY_WIDTH] num_bays = 0 num_bays_max = self.options[Aircraft.BWB.MAX_NUM_BAYS] - root_chord_min = 38.5 # ft + sidewall_min, _ = self.options[Aircraft.Fuselage.CABIN_SIDEWALL_LENGTH_MIN] # ft + width_lava = 36.0 # inch width_galley = 36.0 # inch width_closet = 12.0 # inch @@ -722,7 +733,7 @@ def compute(self, inputs, outputs): if num_bays > num_bays_max and num_bays_max > 0: num_bays = num_bays_max - num_bays_loc = num_bays + # Loop spans 4246~4314 in BWBFUS in sfwate.py iter = 0 while True: num_bays_loc = num_bays @@ -737,11 +748,11 @@ def compute(self, inputs, outputs): area_cabin = area_seats + area_service + area_waste + area_aisle # Calculate cabin dimensions - root_chord = root_chord_min + sidewall = sidewall_min max_width = ( - 2.0 * (-root_chord + np.sqrt(root_chord**2 + tan_sweep * area_cabin)) / tan_sweep + 2.0 * (-sidewall + np.sqrt(sidewall**2 + tan_sweep * area_cabin)) / tan_sweep ) - pax_compart_length = root_chord + tan_sweep * max_width / 2.0 + pax_compart_length = sidewall + tan_sweep * max_width / 2.0 # Enforce maximum number of bays num_bays_tmp = 0.5 + max_width / bay_width_nom @@ -759,15 +770,16 @@ def compute(self, inputs, outputs): num_bays = num_bays_max max_width = num_bays_max * bay_width pax_compart_length = area_cabin / max_width + tan_sweep * max_width / 4.0 - root_chord = pax_compart_length - tan_sweep * max_width / 2.0 + sidewall = pax_compart_length - tan_sweep * max_width / 2.0 else: num_bays = smooth_int_tanh(num_bays_tmp, mu=40.0) if num_bays_loc == num_bays: break - iter = iter + 1 + + iter += 1 if iter > 100: - warnings.warn(f'Number of iteration exceeded 100.') + warnings.warn(f'Number of iteration exceeded 100 in BWBDetailedCabinLayout.') break length = pax_compart_length / rear_spar_percent_chord @@ -779,7 +791,7 @@ def compute(self, inputs, outputs): outputs[Aircraft.Fuselage.CABIN_AREA] = area_cabin outputs[Aircraft.Fuselage.MAX_WIDTH] = max_width outputs[Aircraft.Fuselage.MAX_HEIGHT] = max_height - outputs[Aircraft.Wing.ROOT_CHORD] = root_chord + outputs[Aircraft.Wing.ROOT_CHORD] = sidewall / rear_spar_percent_chord_root outputs['bay_width'] = bay_width # For improvement on using int function on num_bays, see issue #1084. diff --git a/aviary/subsystems/geometry/flops_based/test/test_fuselage.py b/aviary/subsystems/geometry/flops_based/test/test_fuselage.py index f66be7c68d..9fecbe1136 100644 --- a/aviary/subsystems/geometry/flops_based/test/test_fuselage.py +++ b/aviary/subsystems/geometry/flops_based/test/test_fuselage.py @@ -126,22 +126,30 @@ def test_case1(self): prob = self.prob self.aviary_options = AviaryValues() self.aviary_options.set_val(Settings.VERBOSITY, 1, units='unitless') + + kwargs = { + Aircraft.BWB.MAX_BAY_WIDTH: (12.0, 'ft'), + } prob.model.add_subsystem( - 'layout', BWBSimpleCabinLayout(), promotes_outputs=['*'], promotes_inputs=['*'] + 'layout', + BWBSimpleCabinLayout(**kwargs), + promotes_outputs=['*'], + promotes_inputs=['*'], ) prob.setup(check=False, force_alloc_complex=True) prob.set_val(Aircraft.Fuselage.LENGTH, val=137.5, units='ft') prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=64.58, units='ft') prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, val=45.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, val=0.7, units='unitless') prob.run_model() num_bays = prob.get_val(Aircraft.BWB.NUM_BAYS) - assert_near_equal(num_bays, [5], tolerance=1e-9) + assert_near_equal(num_bays, [6], tolerance=1e-9) pax_compart_length = prob.get_val(Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH) assert_near_equal(pax_compart_length, 96.25, tolerance=1e-9) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 63.96019518, tolerance=1e-9) + assert_near_equal(root_chord, 63.96019518 / 0.7, tolerance=1e-9) area_cabin = prob.get_val(Aircraft.Fuselage.CABIN_AREA) assert_near_equal(area_cabin, 5173.1872025, tolerance=1e-9) fuselage_height = prob.get_val(Aircraft.Fuselage.MAX_HEIGHT) @@ -181,7 +189,8 @@ def test_case1(self): prob.setup(check=False, force_alloc_complex=True) prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, val=45.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11, units='unitless') - prob.set_val('Rear_spar_percent_chord', val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, val=0.7, units='unitless') prob.run_model() num_bays = prob.get_val(Aircraft.BWB.NUM_BAYS) @@ -203,7 +212,7 @@ def test_case1(self): assert_near_equal(cabin_area, 4697.33181006, tolerance=1e-9) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 38.5, tolerance=1e-9) + assert_near_equal(root_chord, 38.5 / 0.7, tolerance=1e-9) def test_case2(self): """bwb300_baseline""" @@ -229,7 +238,10 @@ def test_case2(self): prob.setup(check=False, force_alloc_complex=True) prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, val=60.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11, units='unitless') - prob.set_val('Rear_spar_percent_chord', val=0.7, units='unitless') + # prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=50.0, units='ft') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, val=0.7, units='unitless') + prob.run_model() fuselage_width = prob.get_val(Aircraft.Fuselage.MAX_WIDTH) @@ -251,7 +263,7 @@ def test_case2(self): assert_near_equal(cabin_area, 2988.87966179, tolerance=1e-9) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 38.5, tolerance=1e-9) + assert_near_equal(root_chord, 38.5 / 0.7, tolerance=1e-9) def test_case3(self): """bwb300_baseline, MAX_NUM_BAYS=4, MAX_BAY_WIDTH=10.0""" @@ -279,7 +291,8 @@ def test_case3(self): prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, val=60.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11, units='unitless') # prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=50.0, units='ft') - prob.set_val('Rear_spar_percent_chord', val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, val=0.7, units='unitless') prob.run_model() bay_width = prob.get_val('bay_width') @@ -304,7 +317,7 @@ def test_case3(self): assert_near_equal(cabin_area, 2988.87966179, tolerance=1e-9) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 57.40164466, tolerance=1e-9) + assert_near_equal(root_chord, 57.40164466 / 0.7, tolerance=1e-9) @use_tempdirs @@ -326,8 +339,7 @@ def test_case1(self): prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=64.58, units='ft') prob.set_val(Aircraft.Fuselage.MAX_HEIGHT, val=17, units='ft') - prob.set_val(Aircraft.Wing.ROOT_CHORD, val=7.71, units='ft') - prob.set_val('Rear_spar_percent_chord', val=0.7, units='unitless') + prob.set_val(Aircraft.Wing.ROOT_CHORD, val=7.71 / 0.7, units='ft') prob.run_model() ref_diameter = prob.get_val(Aircraft.Fuselage.REF_DIAMETER) diff --git a/aviary/subsystems/geometry/flops_based/test/test_prep_geom.py b/aviary/subsystems/geometry/flops_based/test/test_prep_geom.py index 8956a65dc4..17efe4f58a 100644 --- a/aviary/subsystems/geometry/flops_based/test/test_prep_geom.py +++ b/aviary/subsystems/geometry/flops_based/test/test_prep_geom.py @@ -701,7 +701,7 @@ def test_case1(self): self.aviary_options = AviaryValues() self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) prob.model.add_subsystem( @@ -764,7 +764,7 @@ def test_case2(self): self.aviary_options = AviaryValues() self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], + [0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], units='unitless', ) prob.model.add_subsystem( @@ -803,8 +803,9 @@ def setUp(self): options.set_val(Aircraft.Design.TYPE, val='BWB', units='unitless') options.set_val(Aircraft.Fuselage.SIMPLE_LAYOUT, val=True, units='unitless') options.set_val(Aircraft.BWB.DETAILED_WING_PROVIDED, val=False, units='unitless') - options.set_val(Aircraft.Wing.INPUT_STATION_DISTRIBUTION, [0.0, 0.5, 1.0], units='unitless') + options.set_val(Aircraft.Wing.INPUT_STATION_DISTRIBUTION, [0.5, 1.0], units='unitless') options.set_val(Aircraft.BWB.MAX_NUM_BAYS, 0, units='unitless') + options.set_val(Aircraft.BWB.MAX_BAY_WIDTH, 12, units='ft') options.set_val(Aircraft.BWB.NUM_BAYS, [2], units='unitless') options.set_val(Aircraft.Propulsion.TOTAL_NUM_FUSELAGE_ENGINES, 3, units='unitless') options.set_val(Aircraft.Engine.NUM_ENGINES, np.array([3]), units='unitless') @@ -823,7 +824,8 @@ def setUp(self): prob.set_val(Aircraft.Fuselage.MAX_WIDTH, 64.58, units='ft') prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, 45.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, 0.11, units='unitless') - prob.set_val('Rear_spar_percent_chord', 0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, 0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, 0.7, units='unitless') # BWBComputeDetailedWingDist prob.set_val(Aircraft.Wing.OUTBOARD_SEMISPAN, val=86.75) prob.set_val(Aircraft.Wing.THICKNESS_TO_CHORD, val=0.11) @@ -872,7 +874,7 @@ def test_case1(self): Testing FLOPS data case: Aircraft.BWB.NUM_BAYS -- NBAY = 5 Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH -- XLP = 96.25 - Aircraft.Wing.ROOT_CHORD -- XLW = 63.960195184412598 + Aircraft.Wing.ROOT_CHORD -- XLW = 63.960195184412598, ROOT_CHORD = XLW / 0.7 Aircraft.Fuselage.CABIN_AREA -- ACABIN = 5173.1872025046832 Aircraft.Fuselage.MAX_HEIGHT -- DF = 15.125 Aircraft.Wing.INPUT_STATION_DISTRIBUTION -- ETAW = [0, 32.29, 1] @@ -913,11 +915,11 @@ def test_case1(self): # BWBSimpleCabinLayout num_bays = prob.get_val(Aircraft.BWB.NUM_BAYS) - assert_near_equal(num_bays, [5], tolerance=1e-9) + assert_near_equal(num_bays, [6], tolerance=1e-9) pax_compart_length = prob.get_val(Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH) assert_near_equal(pax_compart_length, 96.25, tolerance=1e-8) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 63.96019518, tolerance=1e-8) + assert_near_equal(root_chord, 63.96019518 / 0.7, tolerance=1e-8) area_cabin = prob.get_val(Aircraft.Fuselage.CABIN_AREA) assert_near_equal(area_cabin, 5173.1872025, tolerance=1e-8) fuselage_height = prob.get_val(Aircraft.Fuselage.MAX_HEIGHT) @@ -1055,7 +1057,7 @@ def setUp(self): options.set_val(Aircraft.Wing.DETAILED_WING, val=True, units='unitless') options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) options.set_val(Aircraft.Propulsion.TOTAL_NUM_FUSELAGE_ENGINES, 3, units='unitless') @@ -1073,12 +1075,13 @@ def setUp(self): # BWBDetailedCabinLayout prob.set_val(Aircraft.BWB.PASSENGER_LEADING_EDGE_SWEEP, val=45.0, units='deg') prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11, units='unitless') - prob.set_val('Rear_spar_percent_chord', val=0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, 0.7, units='unitless') + prob.set_val(Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, 0.7, units='unitless') + prob.set_val(Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, 0.11) # BWBUpdateDetailedWingDist prob.set_val( Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, val=[ - -1.0, 58.03, 0.4491, 0.3884, @@ -1098,7 +1101,6 @@ def setUp(self): prob.set_val( Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, val=[ - -1.0, 0.15, 0.1132, 0.0928, @@ -1117,7 +1119,7 @@ def setUp(self): ) prob.set_val( Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - val=[0.0, 0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9], + val=[0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9], ) prob.set_val(Aircraft.Wing.OUTBOARD_SEMISPAN, 86.75) prob.set_val(Aircraft.Wing.THICKNESS_TO_CHORD, val=0.11) @@ -1175,7 +1177,7 @@ def test_case1(self): Aircraft.Fuselage.MAX_WIDTH -- WF = 80.22 Aircraft.Fuselage.MAX_HEIGHT -- DF = 12.35 Aircraft.Fuselage.CABIN_AREA -- ACABIN = 4697.33 - Aircraft.Wing.ROOT_CHORD -- XLW = 38.50 + Aircraft.Wing.ROOT_CHORD -- XLW = 38.50, ROOT_CHORD = XLW / 0.7 Aircraft.Fuselage.AVG_DIAMETER -- XD = 46.2868886894979 Aircraft.Fuselage.PLANFORM_AREA -- FPAREA = 6710.4740143724875 Aircraft.Wing.AREA -- SW = 12109.9 @@ -1222,7 +1224,7 @@ def test_case1(self): cabin_area = prob.get_val(Aircraft.Fuselage.CABIN_AREA) assert_near_equal(cabin_area, 4697.33181006, tolerance=1e-9) root_chord = prob.get_val(Aircraft.Wing.ROOT_CHORD) - assert_near_equal(root_chord, 38.5, tolerance=1e-9) + assert_near_equal(root_chord, 38.5 / 0.7, tolerance=1e-9) # BWBUpdateDetailedWingDist out1 = prob.get_val('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION') @@ -1361,3 +1363,6 @@ def test_case1(self): if __name__ == '__main__': unittest.main() + # z = BWBDetailedPrepGeomTest() + # z.setUp() + # z.test_case1() diff --git a/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py b/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py index 2ba2a4b564..536eac45b5 100644 --- a/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py +++ b/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py @@ -29,7 +29,7 @@ def test_case1(self): options.set_val(Settings.VERBOSITY, 1, units='unitless') options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) prob.model.add_subsystem( @@ -40,7 +40,6 @@ def test_case1(self): prob.set_val( Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, val=[ - -1.0, 58.03, 0.4491, 0.3884, @@ -60,7 +59,6 @@ def test_case1(self): prob.set_val( Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, val=[ - -1.0, 0.15, 0.1132, 0.0928, @@ -79,13 +77,14 @@ def test_case1(self): ) prob.set_val( Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - val=[0.0, 0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9], + val=[0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9], ) prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=80.220756073526772) prob.set_val(Aircraft.Wing.OUTBOARD_SEMISPAN, val=86.75) prob.set_val(Aircraft.Fuselage.LENGTH, val=112.3001936860821) prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11) - prob.set_val(Aircraft.Wing.ROOT_CHORD, 38.5) + prob.set_val(Aircraft.Wing.ROOT_CHORD, 55.0) + prob.set_val(Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, 0.335) prob.run_model() out1 = prob.get_val('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION') @@ -110,7 +109,7 @@ def test_case1(self): out2 = prob.get_val('BWB_THICKNESS_TO_CHORD_DISTRIBUTION') exp2 = [ - 0.11, + 0.335, 0.11, 0.1132, 0.0928, @@ -132,8 +131,8 @@ def test_case1(self): exp3 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9] assert_near_equal(out3, exp3, tolerance=1e-10) - # partial_data = self.prob.check_partials(out_stream=None, method='cs') - # assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) + partial_data = self.prob.check_partials(out_stream=None, method='cs', step=1.1e-40) + assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) def test_case2(self): """bwb300_baseline""" @@ -142,7 +141,7 @@ def test_case2(self): options.set_val(Settings.VERBOSITY, 1, units='unitless') options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], + [0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], units='unitless', ) # ETAW prob.model.add_subsystem( @@ -152,21 +151,23 @@ def test_case2(self): prob.setup(check=False, force_alloc_complex=True) prob.set_val( Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, - val=[-1.0, 48.25, 33.20, 18.97, 14.19, 10.20, 3.220], + val=[48.25, 33.20, 18.97, 14.19, 10.20, 3.220], ) # CHD prob.set_val( Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, - val=[-1.0, 0.125, 0.125, 0.076, 0.076, 0.076, 0.06], + val=[0.125, 0.125, 0.076, 0.076, 0.076, 0.06], ) # TOC prob.set_val( Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - val=[0.0, 0.0, 0.0, 17.0, 17.0, 17.0], + val=[0.0, 10.0, 17.0, 17.0, 17.0], ) # SWL prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=49.77182929) prob.set_val(Aircraft.Wing.OUTBOARD_SEMISPAN, val=68.43) prob.set_val(Aircraft.Fuselage.LENGTH, val=116.57609631) prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.1792) - prob.set_val(Aircraft.Wing.ROOT_CHORD, 38.5) + prob.set_val(Aircraft.Wing.ROOT_CHORD, 55.0) + prob.set_val(Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, 0.335) + prob.run_model() out1 = prob.get_val('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION') @@ -174,15 +175,98 @@ def test_case2(self): assert_near_equal(out1, exp1, tolerance=1e-8) out2 = prob.get_val('BWB_THICKNESS_TO_CHORD_DISTRIBUTION') - exp2 = [0.1792, 0.1792, 0.125, 0.076, 0.076, 0.076, 0.06] + exp2 = [0.335, 0.1792, 0.125, 0.076, 0.076, 0.076, 0.06] assert_near_equal(out2, exp2, tolerance=1e-10) out3 = prob.get_val('BWB_LOAD_PATH_SWEEP_DISTRIBUTION') - exp3 = [0.0, 0.0, 0.0, 17.0, 17.0, 17.0] + exp3 = [0.0, 0.0, 10.0, 17.0, 17.0, 17.0] assert_near_equal(out3, exp3, tolerance=1e-10) - # partial_data = self.prob.check_partials(out_stream=None, method='cs') - # assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) + partial_data = self.prob.check_partials(out_stream=None, method='cs') + assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) + + def test_case3(self): + # Tests wing_root_index>0 + prob = self.prob + + options = self.aviary_options = AviaryValues() + options.set_val(Settings.VERBOSITY, 1, units='unitless') + options.set_val( + Aircraft.Wing.INPUT_STATION_DISTRIBUTION, + [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + units='unitless', + ) + options.set_val(Aircraft.BWB.WING_ROOT_INDEX, 1, units='unitless') + + prob.model.add_subsystem( + 'dist', BWBUpdateDetailedWingDist(), promotes_outputs=['*'], promotes_inputs=['*'] + ) + setup_model_options(self.prob, options) + prob.setup(check=False, force_alloc_complex=True) + + prob.set_val( + Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, + val=[ + 99.1, + 58.03, + 0.4491, + 0.3884, + 0.3317, + 0.2886, + 0.2537, + 0.2269, + 0.2121, + 0.1983, + 0.1843, + 0.1704, + 0.1565, + 0.1426, + 0.1287, + ], + ) + prob.set_val( + Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, + val=[ + 0.18, + 0.15, + 0.1132, + 0.0928, + 0.0822, + 0.0764, + 0.0742, + 0.0746, + 0.0758, + 0.0758, + 0.0756, + 0.0756, + 0.0758, + 0.076, + 0.076, + ], + ) + prob.set_val( + Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, + val=[0, 0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9], + ) + prob.set_val(Aircraft.Fuselage.MAX_WIDTH, val=80.220756073526772) + prob.set_val(Aircraft.Wing.OUTBOARD_SEMISPAN, val=86.75) + + prob.run_model() + + out1 = prob.get_val('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION') + exp1 = prob.get_val(Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION) + assert_near_equal(out1, exp1, tolerance=1e-8) + + out2 = prob.get_val('BWB_THICKNESS_TO_CHORD_DISTRIBUTION') + exp2 = prob.get_val(Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION) + assert_near_equal(out2, exp2, tolerance=1e-10) + + out3 = prob.get_val('BWB_LOAD_PATH_SWEEP_DISTRIBUTION') + exp3 = prob.get_val(Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION) + assert_near_equal(out3, exp3, tolerance=1e-10) + + partial_data = self.prob.check_partials(out_stream=None, method='cs', step=1.1e-40) + assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) @use_tempdirs @@ -200,11 +284,7 @@ def test_case1(self): self.aviary_options.set_val(Settings.VERBOSITY, 1, units='unitless') self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [ - 0.0, - 0.5, - 1.0, - ], # always set [0, 0.5, 1] but actual value in the middle will be computed + [0.5, 1.0], units='unitless', ) prob.model.add_subsystem( @@ -217,7 +297,7 @@ def test_case1(self): prob.set_val(Aircraft.Fuselage.LENGTH, val=137.5) prob.set_val(Aircraft.Wing.THICKNESS_TO_CHORD, val=0.11) prob.set_val(Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, val=0.11) - prob.set_val(Aircraft.Wing.ROOT_CHORD, 63.96) + prob.set_val(Aircraft.Wing.ROOT_CHORD, 63.96 / 0.7) prob.set_val(Aircraft.Wing.SWEEP, 35.7, units='deg') prob.run_model() @@ -254,7 +334,7 @@ def test_case1(self): self.aviary_options.set_val(Settings.VERBOSITY, 1, units='unitless') self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.5, 1.0], + [0.5, 1.0], units='unitless', ) prob.model.add_subsystem( @@ -287,7 +367,7 @@ def test_case2(self): self.aviary_options.set_val(Settings.VERBOSITY, 1, units='unitless') self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) prob.model.add_subsystem( @@ -336,7 +416,7 @@ def test_case3(self): self.aviary_options.set_val(Settings.VERBOSITY, 1, units='unitless') self.aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], + [0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], units='unitless', ) prob.model.add_subsystem( @@ -365,3 +445,6 @@ def test_case3(self): if __name__ == '__main__': unittest.main() + # z = BWBUpdateDetailedWingDistTest() + # z.setUp() + # z.test_case2() diff --git a/aviary/subsystems/geometry/flops_based/wetted_area_total.py b/aviary/subsystems/geometry/flops_based/wetted_area_total.py index 2716292a1c..2d81f3d2f8 100644 --- a/aviary/subsystems/geometry/flops_based/wetted_area_total.py +++ b/aviary/subsystems/geometry/flops_based/wetted_area_total.py @@ -553,11 +553,16 @@ class BWBWingWettedArea(om.ExplicitComponent): """Calculate wing wetted area of BWB aircraft geometry for FLOPS-based aerodynamics analysis.""" def initialize(self): + add_aviary_option(self, Aircraft.BWB.WING_ROOT_INDEX) add_aviary_option(self, Aircraft.Wing.INPUT_STATION_DISTRIBUTION) add_aviary_option(self, Settings.VERBOSITY) def setup(self): num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + if root < 1: + # Automatically add the centerline. + num_inp_stations += 1 add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') add_aviary_input(self, Aircraft.Wing.GLOVE_AND_BAT, units='ft**2') @@ -574,7 +579,8 @@ def setup(self): self.declare_partials('*', '*', method='cs') def compute(self, inputs, outputs): - verbosity = self.options[Settings.VERBOSITY] + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] wingspan = inputs[Aircraft.Wing.SPAN][0] if wingspan <= 0.0: @@ -583,16 +589,27 @@ def compute(self, inputs, outputs): # This part is repeated in BWBWingPrelim() num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) - bwb_input_station_dist = np.array( - self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION], dtype=float - ) + if root < 1: + # Automatically add the centerline. + num_inp_stations += 1 + + input_station_dist = self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION] + bwb_input_station_dist = np.zeros(num_inp_stations, dtype=width.dtype) + + if root < 1: + bwb_input_station_dist[1:] = input_station_dist + else: + bwb_input_station_dist[:] = input_station_dist + bwb_input_station_dist = np.where( bwb_input_station_dist <= 1.0, bwb_input_station_dist * rate_span + width / wingspan, # if x <= 1.0 bwb_input_station_dist + width / 2.0, # else ) - bwb_input_station_dist[0] = 0.0 - bwb_input_station_dist[1] = width / 2.0 + + if root < 1: + bwb_input_station_dist[0] = 0.0 + bwb_input_station_dist[1] = width / 2.0 ssmw = 0.0 bwb_chord_per_semispan_dist = inputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'] diff --git a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py index c95da5576e..7293e373af 100644 --- a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py +++ b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py @@ -14,11 +14,18 @@ class BWBUpdateDetailedWingDist(om.ExplicitComponent): """ def initialize(self): + add_aviary_option(self, Aircraft.BWB.WING_ROOT_INDEX) add_aviary_option(self, Aircraft.Wing.INPUT_STATION_DISTRIBUTION) add_aviary_option(self, Settings.VERBOSITY) def setup(self): num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + num_out_stations = num_inp_stations + if root < 1: + # Automatically add the centerline. + num_out_stations += 1 + add_aviary_input( self, Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, @@ -37,136 +44,166 @@ def setup(self): shape=num_inp_stations - 1, units='deg', ) - add_aviary_input(self, Aircraft.Fuselage.LENGTH, units='ft') add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') add_aviary_input(self, Aircraft.Wing.OUTBOARD_SEMISPAN, units='ft') - add_aviary_input(self, Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, units='unitless') - add_aviary_input(self, Aircraft.Wing.ROOT_CHORD, units='ft') - self.add_input( - 'rear_spar_percent_chord_side', - 0.7, - units='unitless', - desc='RSPSOB: Rear spar percent chord for BWB at side of body', - ) + + if root < 1: + add_aviary_input(self, Aircraft.Fuselage.LENGTH, units='ft') + add_aviary_input(self, Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, units='unitless') + add_aviary_input(self, Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, units='unitless') + add_aviary_input(self, Aircraft.Wing.ROOT_CHORD, units='ft') self.add_output(Aircraft.Wing.SPAN, units='ft') self.add_output( - 'BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', shape=num_inp_stations, units='unitless' + 'BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', shape=num_out_stations, units='unitless' ) self.add_output( - 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', shape=num_inp_stations, units='unitless' + 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', shape=num_out_stations, units='unitless' ) - self.add_output('BWB_LOAD_PATH_SWEEP_DISTRIBUTION', shape=num_inp_stations - 1, units='deg') + self.add_output('BWB_LOAD_PATH_SWEEP_DISTRIBUTION', shape=num_out_stations - 1, units='deg') def setup_partials(self): + nn = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + if root < 1: + # Automatically add the centerline. + nn += 1 + self.declare_partials( Aircraft.Wing.SPAN, - [ - Aircraft.Fuselage.MAX_WIDTH, - Aircraft.Wing.OUTBOARD_SEMISPAN, - ], - method='cs', + Aircraft.Fuselage.MAX_WIDTH, + val=1.0, ) + self.declare_partials( - 'BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', - [ + Aircraft.Wing.SPAN, + Aircraft.Wing.OUTBOARD_SEMISPAN, + val=2.0, + ) + + if root < 1: + # This is the toughest, so just cs. + wrt = [ Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.MAX_WIDTH, Aircraft.Wing.OUTBOARD_SEMISPAN, Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, Aircraft.Wing.ROOT_CHORD, - 'rear_spar_percent_chord_side', - ], - method='cs', - ) - self.declare_partials( - 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', - [ + ] + self.declare_partials('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', wrt, method='cs') + + rows = np.arange(nn - 2) + 2 + cols = np.arange(nn - 2) + 1 + self.declare_partials( + 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, + rows=rows, + cols=cols, + val=1, + ) + + self.declare_partials( + 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', + Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, + rows=np.array([0]), + cols=np.array([0]), + val=1, + ) + + self.declare_partials( + 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, - ], - ) - self.declare_partials( - 'BWB_LOAD_PATH_SWEEP_DISTRIBUTION', - Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - ) + rows=np.array([1]), + cols=np.array([0]), + val=1, + ) + + rows = np.arange(nn - 3) + 2 + cols = np.arange(nn - 3) + 1 + self.declare_partials( + 'BWB_LOAD_PATH_SWEEP_DISTRIBUTION', + Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, + rows=rows, + cols=cols, + val=1, + ) + + else: + row_col = np.arange(nn) + self.declare_partials( + 'BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', + Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, + rows=row_col, + cols=row_col, + val=1.0, + ) + self.declare_partials( + 'BWB_THICKNESS_TO_CHORD_DISTRIBUTION', + Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, + rows=row_col, + cols=row_col, + val=1.0, + ) + + row_col = np.arange(nn - 1) + self.declare_partials( + 'BWB_LOAD_PATH_SWEEP_DISTRIBUTION', + Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, + rows=row_col, + cols=row_col, + val=1.0, + ) def compute(self, inputs, outputs): - verbosity = self.options[Settings.VERBOSITY] + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] osspan = inputs[Aircraft.Wing.OUTBOARD_SEMISPAN][0] wingspan = width + osspan * 2 outputs[Aircraft.Wing.SPAN] = wingspan - rate_span = (wingspan - width) / wingspan - length = inputs[Aircraft.Fuselage.LENGTH][0] - side_tc = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][0] - root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - rear_spar_percent_chord_side = inputs['rear_spar_percent_chord_side'][0] - if rear_spar_percent_chord_side <= 0.0: - raise ValueError( - 'rear_spar_percent_chord_side must be positive, ' - f'however {rear_spar_percent_chord_side} is provided.' - ) - xl_out = root_chord / rear_spar_percent_chord_side - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'] = inputs[ - Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION - ] - idx = np.where(inputs[Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION] < 5.0) - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][idx] *= rate_span - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][0] = length - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1] = xl_out + if root < 1: + # Adds the point at the centerline, pulling values from BWB geometry. + # From lines 334-356, sfwate.f - outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][0] = side_tc - outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][1] = side_tc - outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][2:] = inputs[ - Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION - ][2:] + length = inputs[Aircraft.Fuselage.LENGTH][0] + cl_tc = inputs[Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO][0] - outputs['BWB_LOAD_PATH_SWEEP_DISTRIBUTION'][:] = inputs[ - Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION - ] + rate_span = (wingspan - width) / wingspan + side_tc = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][0] + root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - def compute_partials(self, inputs, J): - # width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] - # wingspan = width + 2 * osspan - # rate_span = (wingspan - width) / wingspan - # length = inputs[Aircraft.Fuselage.LENGTH][0] - # side_tc = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][0] - # root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - # rear_spar_percent_chord_side = inputs['rear_spar_percent_chord_side'][0] - # xl_out = root_chord / rear_spar_percent_chord_side + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1:] = inputs[ + Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION + ] + idx = np.where(outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'] < 5.0) + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][idx] *= rate_span + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][0] = length + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1] = root_chord - J[Aircraft.Wing.SPAN, Aircraft.Fuselage.MAX_WIDTH] = 1.0 - J[Aircraft.Wing.SPAN, Aircraft.Wing.OUTBOARD_SEMISPAN] = 2.0 + outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][0] = cl_tc + outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][1] = side_tc + outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][2:] = inputs[ + Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION + ][1:] - num_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) - - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][ - 0 - ] = 1.0 - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][ - 1 - ] = 1.0 - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][ - 2: - ] = 0.0 - - diag2_matrix = np.identity(num_stations) - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION] = ( - diag2_matrix - ) - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION][ - 0 - ] = 0.0 - J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION][ - 1 - ] = 0.0 - - diag2_matrix = np.identity(num_stations - 1) - J['BWB_LOAD_PATH_SWEEP_DISTRIBUTION', Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION] = ( - diag2_matrix - ) + outputs['BWB_LOAD_PATH_SWEEP_DISTRIBUTION'][0] = 0.0 + outputs['BWB_LOAD_PATH_SWEEP_DISTRIBUTION'][1:] = inputs[ + Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION + ] + + else: + # Centerline point is already specified in the detailed wing, so just pass it. + + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][:] = inputs[ + Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION + ] + outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][:] = inputs[ + Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION + ] + outputs['BWB_LOAD_PATH_SWEEP_DISTRIBUTION'][:] = inputs[ + Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION + ] class BWBComputeDetailedWingDist(om.ExplicitComponent): @@ -188,12 +225,6 @@ def setup(self): add_aviary_input(self, Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD, units='unitless') add_aviary_input(self, Aircraft.Wing.THICKNESS_TO_CHORD, units='unitless') add_aviary_input(self, Aircraft.Wing.SWEEP, units='deg') - self.add_input( - 'rear_spar_percent_chord_side', - 0.7, - units='unitless', - desc='RSPSOB: Rear spar percent chord for BWB at side of body', - ) self.add_output(Aircraft.Wing.SPAN, units='ft') self.add_output('BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', shape=3, units='unitless') @@ -215,7 +246,6 @@ def setup_partials(self): Aircraft.Fuselage.LENGTH, Aircraft.Wing.ROOT_CHORD, Aircraft.Wing.OUTBOARD_SEMISPAN, - 'rear_spar_percent_chord_side', ], ) self.declare_partials( @@ -232,17 +262,14 @@ def setup_partials(self): Aircraft.Wing.OUTBOARD_SEMISPAN, Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.MAX_WIDTH, - 'rear_spar_percent_chord_side', ], ) def compute(self, inputs, outputs): - verbosity = self.options[Settings.VERBOSITY] - num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) - if num_inp_stations != 3: + if num_inp_stations != 2: raise ValueError( - 'Aircraft.Wing.INPUT_STATION_DISTRIBUTION should be length 3, ' + 'Aircraft.Wing.INPUT_STATION_DISTRIBUTION should be length 1, ' f'however {num_inp_stations} values were provided.' ) @@ -252,25 +279,19 @@ def compute(self, inputs, outputs): outputs[Aircraft.Wing.SPAN] = wingspan length = inputs[Aircraft.Fuselage.LENGTH][0] root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - rear_spar_percent_chord_side = inputs['rear_spar_percent_chord_side'][0] - if rear_spar_percent_chord_side <= 0.0: - raise ValueError( - 'rear_spar_percent_chord_side must be positive, ' - f'however {rear_spar_percent_chord_side} is provided.' - ) - xl_out = root_chord / rear_spar_percent_chord_side + wing_tip_chord = 0.06 * wingspan side_tc = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][0] tc = inputs[Aircraft.Wing.THICKNESS_TO_CHORD][0] sweep = inputs[Aircraft.Wing.SWEEP][0] - tr_out = wing_tip_chord / xl_out - ar_out = 2.0 * (2 * osspan) / (wing_tip_chord + xl_out) + tr_out = wing_tip_chord / root_chord + ar_out = 2.0 * (2 * osspan) / (wing_tip_chord + root_chord) angle = np.tan(sweep / 57.2958) - 2.0 * (1 - tr_out) / (1 + tr_out) / ar_out swp_ld_path = 57.2958 * np.arctan(angle) outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][0] = length - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1] = xl_out + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1] = root_chord outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][2] = wing_tip_chord outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][0] = side_tc @@ -286,14 +307,11 @@ def compute_partials(self, inputs, J): osspan = inputs[Aircraft.Wing.OUTBOARD_SEMISPAN][0] wingspan = width + 2 * osspan root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - rear_spar_percent_chord_side = inputs['rear_spar_percent_chord_side'][0] - xl_out = root_chord / rear_spar_percent_chord_side wing_tip_chord = 0.06 * (width + 2 * osspan) sweep = inputs[Aircraft.Wing.SWEEP][0] - tr_out = 0.06 * (width + 2 * osspan) / xl_out - ar_out = 2.0 * (2 * osspan) / (0.06 * (width + 2 * osspan) + xl_out) + tr_out = 0.06 * (width + 2 * osspan) / root_chord + ar_out = 2.0 * (2 * osspan) / (0.06 * (width + 2 * osspan) + root_chord) angle = np.tan(sweep / 57.2958) - 2.0 * (1 - tr_out) / (1 + tr_out) / ar_out - swp_ld_path = 57.2958 * np.arctan(angle) J[Aircraft.Wing.SPAN, Aircraft.Fuselage.MAX_WIDTH] = 1.0 J[Aircraft.Wing.SPAN, Aircraft.Wing.OUTBOARD_SEMISPAN] = 2.0 @@ -301,16 +319,11 @@ def compute_partials(self, inputs, J): J['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', Aircraft.Fuselage.LENGTH] = [1.0, 0.0, 0.0] J['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', Aircraft.Wing.ROOT_CHORD] = [ 0, - 1 / rear_spar_percent_chord_side, + 1.0, 0, ] J['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', Aircraft.Fuselage.MAX_WIDTH] = [0.0, 0.0, 0.06] J['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', Aircraft.Wing.OUTBOARD_SEMISPAN] = [0.0, 0.0, 0.12] - J['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', 'rear_spar_percent_chord_side'] = [ - 0, - -root_chord / rear_spar_percent_chord_side**2, - 0.0, - ] J['BWB_THICKNESS_TO_CHORD_DISTRIBUTION', Aircraft.Wing.THICKNESS_TO_CHORD] = [0.0, 0.0, 1.0] @@ -326,11 +339,11 @@ def compute_partials(self, inputs, J): dswp_ld_path_dsweep, ] - dtr_out_dspan = 0.06 * rear_spar_percent_chord_side / root_chord + dtr_out_dspan = 0.06 / root_chord dar_out_dspan = ( 2 - * (wing_tip_chord + xl_out - 0.06 * (wingspan - width)) - / (wing_tip_chord + xl_out) ** 2 + * (wing_tip_chord + root_chord - 0.06 * (wingspan - width)) + / (wing_tip_chord + root_chord) ** 2 ) dswp_ld_path_dspan = ( 57.2958 @@ -345,10 +358,8 @@ def compute_partials(self, inputs, J): 2 * dswp_ld_path_dspan, ] - dtr_out_droot_chord = -wing_tip_chord * rear_spar_percent_chord_side / root_chord**2 - dar_out_droot_chord = ( - -2 * (wingspan - width) / (wing_tip_chord + xl_out) ** 2 / rear_spar_percent_chord_side - ) + dtr_out_droot_chord = -wing_tip_chord / root_chord**2 + dar_out_droot_chord = -2 * (wingspan - width) / (wing_tip_chord + root_chord) ** 2 dswp_ld_path_droot_chord = ( 57.2958 / (1 + angle**2) @@ -363,10 +374,10 @@ def compute_partials(self, inputs, J): ] dtr_out_dwidth = 0.0 - dar_out_dwidth = -4 * osspan * 0.06 / (wing_tip_chord + xl_out) ** 2 + dar_out_dwidth = -4 * osspan * 0.06 / (wing_tip_chord + root_chord) ** 2 - dtr_out_dwidth = 0.06 / xl_out - dar_out_dwidth = -4.0 * 0.06 * osspan / (0.06 * (width + 2 * osspan) + xl_out) ** 2 + dtr_out_dwidth = 0.06 / root_chord + dar_out_dwidth = -4.0 * 0.06 * osspan / (0.06 * (width + 2 * osspan) + root_chord) ** 2 dswp_ld_path_dwidth = ( -2 * 57.2958 @@ -384,37 +395,20 @@ def compute_partials(self, inputs, J): dswp_ld_path_dwidth, ] - dtr_out_drear_chord = 0.06 * wingspan / root_chord - dar_out_drear_chord = ( - 2 - * (wingspan - width) - / (wing_tip_chord + xl_out) ** 2 - * root_chord - / rear_spar_percent_chord_side**2 - ) - dswp_ld_path_drear_chord = ( - 57.2958 - / (1 + angle**2) - * ( - 4 * dtr_out_drear_chord / (1 + tr_out) ** 2 / ar_out - + 2 * (2 / (1 + tr_out) - 1) * dar_out_drear_chord / ar_out**2 - ) - ) - J['BWB_LOAD_PATH_SWEEP_DISTRIBUTION', 'rear_spar_percent_chord_side'] = [ - 0.0, - dswp_ld_path_drear_chord, - ] - class BWBWingPrelim(om.ExplicitComponent): """preliminary calculations of wing aspect ratio for BWB using detailed wing information""" def initialize(self): + add_aviary_option(self, Aircraft.BWB.WING_ROOT_INDEX) add_aviary_option(self, Aircraft.Wing.INPUT_STATION_DISTRIBUTION) add_aviary_option(self, Settings.VERBOSITY) def setup(self): num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + if root < 1: + num_inp_stations += 1 add_aviary_input(self, Aircraft.Fuselage.MAX_WIDTH, units='ft') add_aviary_input(self, Aircraft.Wing.GLOVE_AND_BAT, units='ft**2') @@ -433,6 +427,11 @@ def setup_partials(self): def compute(self, inputs, outputs): verbosity = self.options[Settings.VERBOSITY] + num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + if root < 1: + num_inp_stations += 1 + width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] wingspan = inputs[Aircraft.Wing.SPAN][0] if wingspan <= 0.0: @@ -441,17 +440,22 @@ def compute(self, inputs, outputs): rate_span = (wingspan - width) / wingspan # This part is repeated in BWBWingWettedArea() - num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION]) - bwb_input_station_dist = np.array( - self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION], dtype=float - ) - bwb_input_station_dist = np.where( - bwb_input_station_dist <= 1.0, - bwb_input_station_dist * rate_span + width / wingspan, # if x <= 1.0 - bwb_input_station_dist + width / 2.0, # else - ) - bwb_input_station_dist[0] = 0.0 - bwb_input_station_dist[1] = width / 2.0 + input_station_dist = self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION] + bwb_input_station_dist = np.zeros(num_inp_stations, dtype=width.dtype) + + if root < 1: + bwb_input_station_dist[1:] = input_station_dist + + bwb_input_station_dist = np.where( + bwb_input_station_dist <= 1.0, + bwb_input_station_dist * rate_span + width / wingspan, # if x <= 1.0 + bwb_input_station_dist + width / 2.0, # else + ) + bwb_input_station_dist[0] = 0.0 + bwb_input_station_dist[1] = width / 2.0 + + else: + bwb_input_station_dist[:] = input_station_dist glove_and_bat = inputs[Aircraft.Wing.GLOVE_AND_BAT] width = inputs[Aircraft.Fuselage.MAX_WIDTH] @@ -464,10 +468,14 @@ def compute(self, inputs, outputs): C1 = bwb_chord_per_semispan_distribution[0] * wingspan / 2.0 else: C1 = bwb_chord_per_semispan_distribution[0] + if bwb_input_station_dist[0] <= 1.1: Y1 = bwb_input_station_dist[0] * wingspan / 2.0 else: Y1 = bwb_input_station_dist[0] + + # This calculation integrates all stations + # Lines 360-376, sfwate.f for n in range(1, num_inp_stations): if bwb_chord_per_semispan_distribution[n] <= 5.0: C2 = bwb_chord_per_semispan_distribution[n] * wingspan / 2.0 @@ -481,7 +489,9 @@ def compute(self, inputs, outputs): C1 = C2 Y1 = Y2 ssm = ssm + axp + ar = wingspan**2 / (ssm - glove_and_bat) + # Calculated wing area for aerodynamics outputs[Aircraft.Wing.AREA] = ssm outputs[Aircraft.Wing.ASPECT_RATIO] = ar diff --git a/aviary/subsystems/mass/flops_based/fuselage.py b/aviary/subsystems/mass/flops_based/fuselage.py index bca7073445..478b910142 100644 --- a/aviary/subsystems/mass/flops_based/fuselage.py +++ b/aviary/subsystems/mass/flops_based/fuselage.py @@ -160,6 +160,7 @@ def initialize(self): def setup(self): add_aviary_input(self, Aircraft.Design.GROSS_MASS, units='lbm') add_aviary_input(self, Aircraft.Fuselage.CABIN_AREA, units='ft**2') + add_aviary_input(self, Aircraft.Fuselage.MASS_SCALER, units='unitless') add_aviary_output(self, Aircraft.Fuselage.MASS, units='lbm') @@ -170,20 +171,27 @@ def compute(self, inputs, outputs): verbosity = self.options[Settings.VERBOSITY] gross_weight = inputs[Aircraft.Design.GROSS_MASS] * GRAV_ENGLISH_LBM cabin_area = inputs[Aircraft.Fuselage.CABIN_AREA] + mass_scaler = inputs[Aircraft.Fuselage.MASS_SCALER] + if gross_weight <= 0.0: if verbosity > Verbosity.BRIEF: raise om.AnalysisError('Aircraft.Design.GROSS_MASS must be positive.') - outputs[Aircraft.Fuselage.MASS] = 1.8 * gross_weight**0.167 * cabin_area**1.06 + outputs[Aircraft.Fuselage.MASS] = mass_scaler * 1.8 * gross_weight**0.167 * cabin_area**1.06 def compute_partials(self, inputs, J): gross_weight = inputs[Aircraft.Design.GROSS_MASS] * GRAV_ENGLISH_LBM cabin_area = inputs[Aircraft.Fuselage.CABIN_AREA] + mass_scaler = inputs[Aircraft.Fuselage.MASS_SCALER] + J[Aircraft.Fuselage.MASS, Aircraft.Design.GROSS_MASS] = ( - 0.167 * 1.8 * gross_weight**-0.833 * cabin_area**1.06 + mass_scaler * 0.167 * 1.8 * gross_weight**-0.833 * cabin_area**1.06 ) * GRAV_ENGLISH_LBM J[Aircraft.Fuselage.MASS, Aircraft.Fuselage.CABIN_AREA] = ( - 1.06 * 1.8 * gross_weight**0.167 * cabin_area**0.06 + mass_scaler * 1.06 * 1.8 * gross_weight**0.167 * cabin_area**0.06 + ) + J[Aircraft.Fuselage.MASS, Aircraft.Fuselage.MASS_SCALER] = ( + 1.8 * gross_weight**0.167 * cabin_area**1.06 ) @@ -201,18 +209,8 @@ def setup(self): add_aviary_input(self, Aircraft.Fuselage.LENGTH, units='ft') add_aviary_input(self, Aircraft.Wing.ROOT_CHORD, units='ft') add_aviary_input(self, Aircraft.Wing.COMPOSITE_FRACTION, units='unitless') - self.add_input( - 'Rear_spar_percent_chord', - 0.7, - units='unitless', - desc='RSPSOB: Rear spar percent chord for BWB at side of body', - ) - self.add_input( - 'Rear_spar_percent_chord_centerline', - 0.7, - units='unitless', - desc='RSPCHD: Rear spar percent chord for BWB at fuselage centerline', - ) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE) + add_aviary_input(self, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT) add_aviary_output(self, Aircraft.Fuselage.AFTBODY_MASS, units='lbm') add_aviary_output(self, Aircraft.Wing.BWB_AFTBODY_MASS, units='lbm') @@ -226,8 +224,8 @@ def setup_partials(self): Aircraft.Fuselage.CABIN_AREA, Aircraft.Fuselage.LENGTH, Aircraft.Wing.ROOT_CHORD, - 'Rear_spar_percent_chord', - 'Rear_spar_percent_chord_centerline', + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, ], ) self.declare_partials( @@ -239,8 +237,8 @@ def setup_partials(self): Aircraft.Fuselage.LENGTH, Aircraft.Wing.ROOT_CHORD, Aircraft.Wing.COMPOSITE_FRACTION, - 'Rear_spar_percent_chord', - 'Rear_spar_percent_chord_centerline', + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, ], ) @@ -252,22 +250,24 @@ def compute(self, inputs, outputs): cabin_area = inputs[Aircraft.Fuselage.CABIN_AREA] length = inputs[Aircraft.Fuselage.LENGTH] root_chord = inputs[Aircraft.Wing.ROOT_CHORD] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] - rear_spar_percent_chord_centerline = inputs['Rear_spar_percent_chord_centerline'] + rear_spar_percent_chord = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] + rear_spar_percent_chord_centerline = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] comp_frac = inputs[Aircraft.Wing.COMPOSITE_FRACTION] if rear_spar_percent_chord <= 0.0 or rear_spar_percent_chord >= 1.0: if verbosity > Verbosity.BRIEF: - raise ValueError('Rear_spar_percent_chord must be within 0 and 1.') + msg = f'{Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT} must be within 0 and 1.' + raise ValueError(msg) if rear_spar_percent_chord_centerline <= 0.0 or rear_spar_percent_chord_centerline >= 1.0: if verbosity > Verbosity.BRIEF: - raise ValueError('Rear_spar_percent_chord_centerline must be within 0 and 1.') + msg = f'{Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE} must be within 0 and 1.' + raise ValueError() if length <= 0.0: if verbosity > Verbosity.BRIEF: raise ValueError('Aircraft.Fuselage.LENGTH must be positive.') aftbody_area = fuse_area - cabin_area - aftbody_tr = ((1.0 - rear_spar_percent_chord) * root_chord / rear_spar_percent_chord) / ( + aftbody_tr = ((1.0 - rear_spar_percent_chord) * root_chord) / ( (1.0 - rear_spar_percent_chord_centerline) * length ) aftbody_weight = ( @@ -288,13 +288,13 @@ def compute_partials(self, inputs, J): cabin_area = inputs[Aircraft.Fuselage.CABIN_AREA] length = inputs[Aircraft.Fuselage.LENGTH] root_chord = inputs[Aircraft.Wing.ROOT_CHORD] - rear_spar_percent_chord = inputs['Rear_spar_percent_chord'] - rear_spar_percent_chord_centerline = inputs['Rear_spar_percent_chord_centerline'] + rear_spar_percent_chord = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] + rear_spar_percent_chord_centerline = inputs[Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] comp_frac = inputs[Aircraft.Wing.COMPOSITE_FRACTION] fac = 1.0 - 0.17 * comp_frac aftbody_area = fuse_area - cabin_area - aftbody_tr = ((1.0 - rear_spar_percent_chord) * root_chord / rear_spar_percent_chord) / ( + aftbody_tr = ((1.0 - rear_spar_percent_chord) * root_chord) / ( (1.0 - rear_spar_percent_chord_centerline) * length ) aftbody_weight = ( @@ -328,7 +328,7 @@ def compute_partials(self, inputs, J): J[Aircraft.Wing.BWB_AFTBODY_MASS, Aircraft.Fuselage.CABIN_AREA] = ( J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.Fuselage.CABIN_AREA] * fac ) - daftbody_tr_droot_chord = ((1.0 - rear_spar_percent_chord) / rear_spar_percent_chord) / ( + daftbody_tr_droot_chord = (1.0 - rear_spar_percent_chord) / ( (1.0 - rear_spar_percent_chord_centerline) * length ) J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.Wing.ROOT_CHORD] = ( @@ -341,9 +341,9 @@ def compute_partials(self, inputs, J): J[Aircraft.Wing.BWB_AFTBODY_MASS, Aircraft.Wing.ROOT_CHORD] = ( J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.Wing.ROOT_CHORD] * fac ) - daftbody_tr_dlength = -( - (1.0 - rear_spar_percent_chord) * root_chord / rear_spar_percent_chord - ) / ((1.0 - rear_spar_percent_chord_centerline) * length**2) + daftbody_tr_dlength = -((1.0 - rear_spar_percent_chord) * root_chord) / ( + (1.0 - rear_spar_percent_chord_centerline) * length**2 + ) J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.Fuselage.LENGTH] = ( (1.0 + 0.05 * num_fuse_eng) * 0.53 @@ -355,33 +355,30 @@ def compute_partials(self, inputs, J): J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.Fuselage.LENGTH] * fac ) daftbody_tr_drspc = ( - -1.0 - / rear_spar_percent_chord**2 - * root_chord - / ((1.0 - rear_spar_percent_chord_centerline) * length) + -1.0 * root_chord / ((1.0 - rear_spar_percent_chord_centerline) * length) ) - J[Aircraft.Fuselage.AFTBODY_MASS, 'Rear_spar_percent_chord'] = ( + J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] = ( (1.0 + 0.05 * num_fuse_eng) * 0.53 * aftbody_area * gross_weight**0.2 * daftbody_tr_drspc ) / GRAV_ENGLISH_LBM - J[Aircraft.Wing.BWB_AFTBODY_MASS, 'Rear_spar_percent_chord'] = ( - J[Aircraft.Fuselage.AFTBODY_MASS, 'Rear_spar_percent_chord'] * fac + J[Aircraft.Wing.BWB_AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] = ( + J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT] * fac + ) + daftbody_tr_drspcc = ((1.0 - rear_spar_percent_chord) * root_chord) / ( + (1.0 - rear_spar_percent_chord_centerline) ** 2 * length ) - daftbody_tr_drspcc = ( - (1.0 - rear_spar_percent_chord) * root_chord / rear_spar_percent_chord - ) / ((1.0 - rear_spar_percent_chord_centerline) ** 2 * length) - J[Aircraft.Fuselage.AFTBODY_MASS, 'Rear_spar_percent_chord_centerline'] = ( + J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] = ( (1.0 + 0.05 * num_fuse_eng) * 0.53 * aftbody_area * gross_weight**0.2 * daftbody_tr_drspcc ) / GRAV_ENGLISH_LBM - J[Aircraft.Wing.BWB_AFTBODY_MASS, 'Rear_spar_percent_chord_centerline'] = ( - J[Aircraft.Fuselage.AFTBODY_MASS, 'Rear_spar_percent_chord_centerline'] * fac + J[Aircraft.Wing.BWB_AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] = ( + J[Aircraft.Fuselage.AFTBODY_MASS, Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE] * fac ) J[Aircraft.Wing.BWB_AFTBODY_MASS, Aircraft.Wing.COMPOSITE_FRACTION] = ( -0.17 * aftbody_weight diff --git a/aviary/subsystems/mass/flops_based/nacelle.py b/aviary/subsystems/mass/flops_based/nacelle.py index 44ffe26294..057c97c87d 100644 --- a/aviary/subsystems/mass/flops_based/nacelle.py +++ b/aviary/subsystems/mass/flops_based/nacelle.py @@ -3,6 +3,7 @@ from aviary.constants import GRAV_ENGLISH_LBM from aviary.subsystems.mass.flops_based.distributed_prop import nacelle_count_factor +from aviary.variable_info.enums import AircraftTypes from aviary.variable_info.functions import add_aviary_input, add_aviary_option, add_aviary_output from aviary.variable_info.variables import Aircraft @@ -21,6 +22,8 @@ class NacelleMass(om.ExplicitComponent): def initialize(self): add_aviary_option(self, Aircraft.Engine.NUM_ENGINES) + add_aviary_option(self, Aircraft.Engine.NUM_WING_ENGINES) + add_aviary_option(self, Aircraft.Design.TYPE) def setup(self): num_engine_type = len(self.options[Aircraft.Engine.NUM_ENGINES]) @@ -42,13 +45,13 @@ def setup_partials(self): shape = np.arange(num_engine_type) self.declare_partials( - Aircraft.Nacelle.MASS, Aircraft.Nacelle.AVG_DIAMETER, rows=shape, cols=shape, val=1.0 + Aircraft.Nacelle.MASS, Aircraft.Nacelle.AVG_DIAMETER, rows=shape, cols=shape ) self.declare_partials( - Aircraft.Nacelle.MASS, Aircraft.Nacelle.AVG_LENGTH, rows=shape, cols=shape, val=1.0 + Aircraft.Nacelle.MASS, Aircraft.Nacelle.AVG_LENGTH, rows=shape, cols=shape ) self.declare_partials( - Aircraft.Nacelle.MASS, Aircraft.Nacelle.MASS_SCALER, rows=shape, cols=shape, val=1.0 + Aircraft.Nacelle.MASS, Aircraft.Nacelle.MASS_SCALER, rows=shape, cols=shape ) self.declare_partials( Aircraft.Nacelle.MASS, @@ -66,7 +69,11 @@ def compute(self, inputs, outputs): # Original FLOPS nacelle equation was mass of all nacelles - here we average it to nacelle # mass per individual engine - count_factor = nacelle_count_factor(num_eng) / num_eng + if self.options[Aircraft.Design.TYPE] == AircraftTypes.BLENDED_WING_BODY: + count_factor = self.options[Aircraft.Engine.NUM_WING_ENGINES] + else: + count_factor = nacelle_count_factor(num_eng) / num_eng + # This should be distributed thrust factor, see issue #1096. thrust = inputs[Aircraft.Engine.SCALED_SLS_THRUST] @@ -80,7 +87,11 @@ def compute_partials(self, inputs, J): avg_length = inputs[Aircraft.Nacelle.AVG_LENGTH] scaler = inputs[Aircraft.Nacelle.MASS_SCALER] - count_factor = nacelle_count_factor(num_eng) / num_eng + if self.options[Aircraft.Design.TYPE] == AircraftTypes.BLENDED_WING_BODY: + count_factor = self.options[Aircraft.Engine.NUM_WING_ENGINES] + else: + count_factor = nacelle_count_factor(num_eng) / num_eng + # This should be distributed thrust factor, see issue #1096. thrust = inputs[Aircraft.Engine.SCALED_SLS_THRUST] diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py index 72e9b23d11..bcb724f62a 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_detailed.py @@ -540,7 +540,7 @@ def test_case1(self): aviary_options.set_val(Aircraft.Engine.NUM_WING_ENGINES, [0], units='unitless') aviary_options.set_val(Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES, 0, units='unitless') aviary_options.set_val( - Aircraft.Wing.INPUT_STATION_DISTRIBUTION, [0.0, 0.5, 1.0], units='unitless' + Aircraft.Wing.INPUT_STATION_DISTRIBUTION, [0.5, 1.0], units='unitless' ) aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') aviary_options.set_val(Aircraft.Wing.NUM_INTEGRATION_STATIONS, 50, units='unitless') @@ -611,7 +611,7 @@ def test_case1(self): aviary_options.set_val(Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES, 0, units='unitless') aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], + [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1], units='unitless', ) aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') @@ -731,7 +731,7 @@ def test_case2(self): aviary_options.set_val(Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES, 0, units='unitless') aviary_options.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - [0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], + [0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0], units='unitless', ) aviary_options.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0, units='unitless') diff --git a/aviary/subsystems/mass/flops_based/test/test_wing_group.py b/aviary/subsystems/mass/flops_based/test/test_wing_group.py index b043b94239..a12979da0b 100644 --- a/aviary/subsystems/mass/flops_based/test/test_wing_group.py +++ b/aviary/subsystems/mass/flops_based/test/test_wing_group.py @@ -52,8 +52,12 @@ def test_case(self): prob.model.set_input_defaults( 'BWB_CHORD_PER_SEMISPAN_DISTRIBUTION', [137.5, 91.3717, 14.2848], units='unitless' ) - prob.model.set_input_defaults('Rear_spar_percent_chord', 0.7, units='unitless') - prob.model.set_input_defaults('Rear_spar_percent_chord_centerline', 0.7, units='unitless') + prob.model.set_input_defaults( + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, 0.7, units='unitless' + ) + prob.model.set_input_defaults( + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, 0.7, units='unitless' + ) prob.setup(check=False, force_alloc_complex=True) diff --git a/aviary/subsystems/mass/flops_based/wing_detailed.py b/aviary/subsystems/mass/flops_based/wing_detailed.py index fb6c565bfd..0b1f7b00bd 100644 --- a/aviary/subsystems/mass/flops_based/wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/wing_detailed.py @@ -303,6 +303,7 @@ class BWBDetailedWingBendingFact(om.ExplicitComponent): # Basically, Engine.WING_LOCATIONS is ignored if there are one or fewer wing engines def initialize(self): + add_aviary_option(self, Aircraft.BWB.WING_ROOT_INDEX) add_aviary_option(self, Aircraft.Engine.NUM_ENGINES) add_aviary_option(self, Aircraft.Engine.NUM_WING_ENGINES) add_aviary_option(self, Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES) @@ -315,6 +316,10 @@ def initialize(self): def setup(self): input_station_distribution = self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION] num_input_stations = len(input_station_distribution) + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + if root < 1: + num_input_stations += 1 + total_num_wing_engines = self.options[Aircraft.Propulsion.TOTAL_NUM_WING_ENGINES] num_engine_type = len(self.options[Aircraft.Engine.NUM_ENGINES]) @@ -358,25 +363,32 @@ def setup_partials(self): self.declare_partials('*', '*', method='cs') def compute(self, inputs, outputs): - verbosity = self.options[Settings.VERBOSITY] num_integration_stations = self.options[Aircraft.Wing.NUM_INTEGRATION_STATIONS] + root = self.options[Aircraft.BWB.WING_ROOT_INDEX] + width = inputs[Aircraft.Fuselage.MAX_WIDTH][0] wingspan = inputs[Aircraft.Wing.SPAN][0] rate_span = (wingspan - width) / wingspan - bwb_input_station_dist = np.array( - self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION], dtype=width.dtype - ) - if not self.options[Aircraft.BWB.DETAILED_WING_PROVIDED]: - bwb_input_station_dist[1] = width / 2.0 + input_station_dist = self.options[Aircraft.Wing.INPUT_STATION_DISTRIBUTION] + + if root < 1: + bwb_input_station_dist = np.zeros(len(input_station_dist) + 1, dtype=width.dtype) + bwb_input_station_dist[1:] = input_station_dist + if not self.options[Aircraft.BWB.DETAILED_WING_PROVIDED]: + bwb_input_station_dist[1] = width / 2.0 + else: + bwb_input_station_dist = np.where( + bwb_input_station_dist <= 1.0, + bwb_input_station_dist * rate_span + width / wingspan, # if x <= 1.0 + bwb_input_station_dist + width / 2.0, # else + ) + bwb_input_station_dist[0] = 0.0 + bwb_input_station_dist[1] = width / 2.0 + else: - bwb_input_station_dist = np.where( - bwb_input_station_dist <= 1.0, - bwb_input_station_dist * rate_span + width / wingspan, # if x <= 1.0 - bwb_input_station_dist + width / 2.0, # else - ) - bwb_input_station_dist[0] = 0.0 - bwb_input_station_dist[1] = width / 2.0 + bwb_input_station_dist = np.asarray(input_station_dist) + inp_stations_mod = [] for x in bwb_input_station_dist: if x > 1.0: @@ -384,11 +396,16 @@ def compute(self, inputs, outputs): else: inp_stations_mod.append(x) inp_stations_mod = np.array(inp_stations_mod) - # For BWB, always start from inp_stations_mod[1], not inp_stations_mod[0] - inp_stations_mod = inp_stations_mod[1:] + + if root == 0: + # Remove prepended centerline for this calculation. + # Otherwise, wing starts where the user specified. + root = 1 + + inp_stations_mod = inp_stations_mod[root:] load_path_sweep = inputs['BWB_LOAD_PATH_SWEEP_DISTRIBUTION'] - load_path_sweep_mod = np.array(load_path_sweep[1:]) + load_path_sweep_mod = np.array(load_path_sweep[root:]) ar = inputs[Aircraft.Wing.ASPECT_RATIO][0] arref = inputs[Aircraft.Wing.ASPECT_RATIO_REFERENCE][0] @@ -405,7 +422,7 @@ def compute(self, inputs, outputs): chord_mod.append(2 * x / wingspan) else: chord_mod.append(x * ar_scale_factor) - chord_mod = np.array(chord_mod) + chord_mod = np.array(chord_mod)[root:] fstrt = inputs[Aircraft.Wing.STRUT_BRACING_FACTOR] faert = inputs[Aircraft.Wing.AEROELASTIC_TAILORING_FACTOR] @@ -413,7 +430,7 @@ def compute(self, inputs, outputs): thickness_to_chord = inputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'] tc = inputs[Aircraft.Wing.THICKNESS_TO_CHORD] tcref = inputs[Aircraft.Wing.THICKNESS_TO_CHORD_REFERENCE] - thickness_to_chord_mod = thickness_to_chord[1:] * tc[0] / tcref[0] + thickness_to_chord_mod = thickness_to_chord[root:] * tc[0] / tcref[0] # NOTE changes to FLOPS routines based on LEAPS1 improved multiengine effort # odd numbers of wing mounted engines assume the "odd" engine out is not on the @@ -449,7 +466,7 @@ def compute(self, inputs, outputs): chord_interp = InterpND( method='slinear', points=(inp_stations_mod), x_interp=integration_stations ) - chord_int_stations = chord_interp.evaluate_spline(chord_mod[1:], compute_derivative=False) + chord_int_stations = chord_interp.evaluate_spline(chord_mod, compute_derivative=False) # Scale chord_int_stations *= ar_scale_factor diff --git a/aviary/subsystems/test/test_flops_based_premission.py b/aviary/subsystems/test/test_flops_based_premission.py index 78bee8c5db..dad6e8e4b0 100644 --- a/aviary/subsystems/test/test_flops_based_premission.py +++ b/aviary/subsystems/test/test_flops_based_premission.py @@ -1,3 +1,4 @@ +from copy import deepcopy import unittest import openmdao.api as om @@ -6,6 +7,7 @@ from parameterized import parameterized from aviary.core.aviary_problem import AviaryProblem +from aviary.models.missions.energy_state_default import phase_info from aviary.subsystems.premission import CorePreMission from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values @@ -488,6 +490,99 @@ def test_case_geom(self): excludes=['*detailed_wing.*'], # does not work? ) + def test_case_root_chord_idx_1(self): + # Test detailed wing where root chord starts at index 1. + # Truth values come from FLOPS case. + local_phase_info = deepcopy(phase_info) + prob = AviaryProblem() + + prob.load_inputs( + 'validation_cases/validation_data/test_models/bwb_root_chord1.csv', + local_phase_info, + verbosity=0, + ) + prob.aviary_inputs.set_val(Aircraft.Design.GROSS_MASS, 156019.0, units='lbm') + + prob.check_and_preprocess_inputs() + prob.build_model() + + prob.add_design_variables() + prob.add_objective(objective_type='fuel_burned') + + prob.setup() + + prob.set_initial_guesses() + + prob.final_setup() + prob.run_model() + + rtol = 1e-3 + val = prob.get_val(Aircraft.Wing.BENDING_MATERIAL_MASS, units='lbm') + assert_near_equal(val, 2394.3, rtol) # 2406 FLOPS + + val = prob.get_val(Aircraft.Wing.SHEAR_CONTROL_MASS, units='lbm') + assert_near_equal(val, 6017.4, rtol) + + val = prob.get_val(Aircraft.Wing.MISC_MASS, units='lbm') + assert_near_equal(val, 2236.5, rtol) + + val = prob.get_val(Aircraft.Wing.BWB_AFTBODY_MASS, units='lbm') + assert_near_equal(val, 2476.2, rtol) # 2504 FLOPS + + val = prob.get_val(Aircraft.Fuselage.MASS, units='lbm') + assert_near_equal(val, 26965.0, rtol) + + val = prob.get_val(Aircraft.Design.EMPTY_MASS, units='lbm') + assert_near_equal(val, 91641.0, rtol) # 91688 FLOPS + + def test_case_root_chord_idx_2(self): + # Test detailed wing where root chord starts at index 1. + # Truth values come from FLOPS case. + local_phase_info = deepcopy(phase_info) + prob = AviaryProblem() + + prob.load_inputs( + 'validation_cases/validation_data/test_models/bwb_root_chord2.csv', + local_phase_info, + verbosity=0, + ) + prob.aviary_inputs.set_val(Aircraft.Design.GROSS_MASS, 156019.0, units='lbm') + + prob.check_and_preprocess_inputs() + prob.build_model() + + prob.add_design_variables() + prob.add_objective(objective_type='fuel_burned') + + prob.setup() + + prob.set_initial_guesses() + + prob.final_setup() + prob.run_model() + + rtol = 1e-3 + val = prob.get_val(Aircraft.Wing.AREA, units='ft**2') + assert_near_equal(val, 3715.5, rtol) # 3715 FLOPS + + val = prob.get_val(Aircraft.Wing.BENDING_MATERIAL_MASS, units='lbm') + assert_near_equal(val, 2394.8, rtol) # 2406 FLOPS + + val = prob.get_val(Aircraft.Wing.SHEAR_CONTROL_MASS, units='lbm') + assert_near_equal(val, 5994.2, rtol) + + val = prob.get_val(Aircraft.Wing.MISC_MASS, units='lbm') + assert_near_equal(val, 2236.5, rtol) + + val = prob.get_val(Aircraft.Wing.BWB_AFTBODY_MASS, units='lbm') + assert_near_equal(val, 2476.2, rtol) # 2504 FLOPS + + val = prob.get_val(Aircraft.Fuselage.MASS, units='lbm') + assert_near_equal(val, 26964.0, rtol) + + val = prob.get_val(Aircraft.Design.EMPTY_MASS, units='lbm') + assert_near_equal(val, 91585.2, rtol) # 91611 FLOPS + @use_tempdirs class BWBPreMissionGroupCSVTest1(unittest.TestCase): @@ -534,10 +629,10 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Wing.SPAN], 238.08, tol) # BWBSimpleCabinLayout assert_near_equal(prob[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH], 96.25, tol) - assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 63.96, tol) + assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 63.96 / 0.7, tol) assert_near_equal(prob[Aircraft.Fuselage.CABIN_AREA], 5173.187202504683, tol) assert_near_equal(prob[Aircraft.Fuselage.MAX_HEIGHT], 15.125, tol) - assert_near_equal(prob[Aircraft.BWB.NUM_BAYS], 5.0, 1e-4) + assert_near_equal(prob[Aircraft.BWB.NUM_BAYS], 6.0, 1e-4) # BWBFuselagePrelim assert_near_equal(prob[Aircraft.Fuselage.REF_DIAMETER], 39.8525, tol) assert_near_equal(prob[Aircraft.Fuselage.PLANFORM_AREA], 7390.267432149546, tol) @@ -630,7 +725,7 @@ def test_case_geom_mass(self): # TransportEngineOilMass assert_near_equal(prob[Aircraft.Propulsion.TOTAL_ENGINE_OIL_MASS], 346.93557352, tol) # BWBFurnishingsGroupMass - assert_near_equal(prob[Aircraft.Furnishings.MASS], 61482.097969438299, tol) + assert_near_equal(prob[Aircraft.Furnishings.MASS], 64632.2417937, tol) # TransportHydraulicsGroupMass assert_near_equal(prob[Aircraft.Hydraulics.MASS], 7368.5077321194321, tol) # PassengerServiceMass @@ -692,15 +787,13 @@ def test_case_geom_mass(self): # PropulsionMass assert_near_equal(prob[Aircraft.Propulsion.MASS], 61597.102467771889, tol) # SystemsEquipMass - assert_near_equal( - prob[Aircraft.Design.SYSTEMS_AND_EQUIPMENT_MASS], 98848.9061107412710, tol - ) + assert_near_equal(prob[Aircraft.Design.SYSTEMS_AND_EQUIPMENT_MASS], 101999.04844105, tol) # EmptyMass - assert_near_equal(prob[Aircraft.Design.EMPTY_MASS], 434037.32820147, tol) + assert_near_equal(prob[Aircraft.Design.EMPTY_MASS], 437187.15893079, tol) # OperatingMass - assert_near_equal(prob[Mission.OPERATING_MASS], 455464.65969526308, tol) + assert_near_equal(prob[Mission.OPERATING_MASS], 458614.76964429, tol) # ZeroFuelMass - assert_near_equal(prob[Mission.ZERO_FUEL_MASS], 553276.65969526302, tol) + assert_near_equal(prob[Mission.ZERO_FUEL_MASS], 556426.76964429, tol) def test_case_all_subsystems(self): """ @@ -782,7 +875,7 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Wing.SPAN], 253.720756, tol) # DetailedCabinLayout assert_near_equal(prob[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH], 78.61013558, tol) - assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 38.5, tol) + assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 38.5 / 0.7, tol) assert_near_equal(prob[Aircraft.Fuselage.CABIN_AREA], 4697.33181006, tol) assert_near_equal(prob[Aircraft.Fuselage.MAX_HEIGHT], 12.35302131, tol) assert_near_equal(prob[Aircraft.BWB.NUM_BAYS], 7.0, tol) @@ -1031,7 +1124,7 @@ def test_case_geom(self): # DetailedCabinLayout assert_near_equal(prob[Aircraft.Fuselage.MAX_WIDTH], 49.77182929, tolerance=1e-9) assert_near_equal(prob[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH], 81.60326742, tol) - assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 38.5, tol) + assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 38.5 / 0.7, tol) assert_near_equal(prob[Aircraft.Fuselage.CABIN_AREA], 2988.879661796, tol) assert_near_equal(prob[Aircraft.Fuselage.MAX_HEIGHT], 20.89043646, tol) assert_near_equal(prob[Aircraft.BWB.NUM_BAYS], 4.0, tol) @@ -1234,3 +1327,6 @@ def test_case_all_subsystems(self): if __name__ == '__main__': unittest.main() + # z = BWBPreMissionGroupTest() + # z.setUp() + # z.test_case_all_subsystems() diff --git a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py index 0ac905cc50..f791b1facc 100644 --- a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py @@ -110,19 +110,19 @@ def test_bench_bwb_FwFm_SNOPT(self): # There are no truth values for these. assert_near_equal( prob.get_val(Aircraft.Design.GROSS_MASS, units='lbm'), - 782430.3, + 787036.9, tolerance=rtol, ) assert_near_equal( prob.get_val(Mission.OPERATING_MASS, units='lbm'), - 445429.9, + 449095.9, tolerance=rtol, ) assert_near_equal( prob.get_val(Mission.TOTAL_FUEL_MASS, units='lbm'), - 239188.4, + 240128.9, tolerance=rtol, ) @@ -198,7 +198,7 @@ def test_bench_bwb300_FwFm_SNOPT(self): # There are no truth values for these. assert_near_equal( prob.get_val(Mission.GROSS_MASS, units='lbm'), - 564852.60335347, + 564856.71154669, tolerance=rtol, ) @@ -219,6 +219,6 @@ def test_bench_bwb300_FwFm_SNOPT(self): if __name__ == '__main__': # unittest.main() - test = BWB300ProblemPhaseTestCase() + test = BWBProblemPhaseTestCase() test.setUp() - test.test_bench_bwb300_FwFm_SNOPT() + test.test_bench_bwb_FwFm_SNOPT() diff --git a/aviary/validation_cases/validation_data/test_data/bwb300_baseline_FLOPS_data.py b/aviary/validation_cases/validation_data/test_data/bwb300_baseline_FLOPS_data.py index f1fc9bb9ed..3ecc60c156 100644 --- a/aviary/validation_cases/validation_data/test_data/bwb300_baseline_FLOPS_data.py +++ b/aviary/validation_cases/validation_data/test_data/bwb300_baseline_FLOPS_data.py @@ -114,6 +114,7 @@ inputs.set_val(Aircraft.Fuselage.WETTED_AREA_SCALER, 1.0) # SWETF inputs.set_val(Aircraft.Fuselage.LAMINAR_FLOW_LOWER, 0.0) # TRLB inputs.set_val(Aircraft.Fuselage.LAMINAR_FLOW_UPPER, 0.0) # TRUB +inputs.set_val(Aircraft.Fuselage.HEIGHT_TO_WIDTH_RATIO, 0.1792) # TCF # Horizontal Tail # --------------------------- @@ -212,7 +213,7 @@ inputs.set_val( Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, - np.array([-1.0, 48.25, 33.20, 18.97, 14.19, 10.20, 3.220]), + np.array([48.25, 33.20, 18.97, 14.19, 10.20, 3.220]), ) # CHD inputs.set_val(Aircraft.Wing.COMPOSITE_FRACTION, 0.85) # FCOMP inputs.set_val(Aircraft.Wing.CONTROL_SURFACE_AREA_RATIO, 0.3) # FLAPR @@ -223,14 +224,14 @@ inputs.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - np.array([0.0, 0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0]), # ETAW + np.array([0.0, 0.2075, 0.415, 0.6927, 0.928, 1.0]), # ETAW ) inputs.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0) # PDIST inputs.set_val( Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - np.array([0.0, 0.0, 0.0, 17.0, 17.0, 17.0]), + np.array([0.0, 0.0, 17.0, 17.0, 17.0]), 'deg', # SWL ) inputs.set_val(Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, 2.0) # CAM @@ -248,7 +249,7 @@ inputs.set_val( Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, - np.array([-1.0, 0.125, 0.125, 0.076, 0.076, 0.076, 0.06]), # TOC + np.array([0.125, 0.125, 0.076, 0.076, 0.076, 0.06]), # TOC ) inputs.set_val(Aircraft.Wing.ULTIMATE_LOAD_FACTOR, 3.75) # ULF inputs.set_val(Aircraft.Wing.VAR_SWEEP_MASS_PENALTY, 0.0) # VARSWP @@ -412,7 +413,7 @@ outputs.set_val(Aircraft.Wing.SURFACE_CONTROL_MASS, 8093.1997108029764, 'lbm') # WSC outputs.set_val(Aircraft.Wing.ASPECT_RATIO, 4.84361005) # AR outputs.set_val(Aircraft.Wing.MASS, 50145.60120438, 'lbm') # WWING -outputs.set_val(Aircraft.Wing.ROOT_CHORD, 38.5, 'ft') # XLW +outputs.set_val(Aircraft.Wing.ROOT_CHORD, 55.0, 'ft') # XLOUT outputs.set_val(Aircraft.Wing.AREA, 8421.7146805052689, 'ft**2') # SW outputs.set_val(Aircraft.Wing.WETTED_AREA, 17302.04910213, 'ft**2') # SWET(1) outputs.set_val(Aircraft.Wing.ASPECT_RATIO_REFERENCE, 4.84361005) # ARREF diff --git a/aviary/validation_cases/validation_data/test_data/bwb_detailed_FLOPS_data.py b/aviary/validation_cases/validation_data/test_data/bwb_detailed_FLOPS_data.py index 8d2294ccbb..c8c16f9180 100644 --- a/aviary/validation_cases/validation_data/test_data/bwb_detailed_FLOPS_data.py +++ b/aviary/validation_cases/validation_data/test_data/bwb_detailed_FLOPS_data.py @@ -196,8 +196,6 @@ Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION, np.array( [ - -1.0, - 58.03, 0.4491, 0.3884, 0.3317, @@ -221,14 +219,14 @@ inputs.set_val( Aircraft.Wing.INPUT_STATION_DISTRIBUTION, - np.array([0.0, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1]), + np.array([0.4, 0.45, 0.5, 0.55, 0.6, 0.6499, 0.7, 0.75, 0.8, 0.85, 0.8999, 0.95, 1]), ) # ETAW inputs.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0) # PDIST inputs.set_val( Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION, - np.array([0.0, 0, 0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9]), + np.array([0, 0, 0, 0, 0, 0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9]), 'deg', ) # SWL inputs.set_val(Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, 2.0) # CAM @@ -248,8 +246,6 @@ Aircraft.Wing.THICKNESS_TO_CHORD_DISTRIBUTION, np.array( [ - -1.0, - 0.15, 0.1132, 0.0928, 0.0822, @@ -417,7 +413,7 @@ outputs.set_val(Aircraft.Wing.ASPECT_RATIO, 5.36951675) # AR outputs.set_val(Aircraft.Wing.ASPECT_RATIO_REFERENCE, 5.36951675) # ARREF outputs.set_val(Aircraft.Wing.MASS, 68995.43251482, 'lbm') # WWING 68995.460470895763 -outputs.set_val(Aircraft.Wing.ROOT_CHORD, 38.5, 'ft') # XLW +outputs.set_val(Aircraft.Wing.ROOT_CHORD, 55.0, 'ft') # XLOUT outputs.set_val(Aircraft.Wing.AREA, 12109.879719468739, 'ft**2') # SW outputs.set_val(Aircraft.Wing.LOAD_FRACTION, 0.46761341784858923) # PCTL outputs.set_val(Aircraft.Wing.WETTED_AREA, 24713.661297561481, 'ft**2') # SWET(1) diff --git a/aviary/validation_cases/validation_data/test_data/bwb_simple_FLOPS_data.py b/aviary/validation_cases/validation_data/test_data/bwb_simple_FLOPS_data.py index 2f27d84943..8f0b71a6c2 100644 --- a/aviary/validation_cases/validation_data/test_data/bwb_simple_FLOPS_data.py +++ b/aviary/validation_cases/validation_data/test_data/bwb_simple_FLOPS_data.py @@ -199,7 +199,7 @@ inputs.set_val(Aircraft.Wing.DETAILED_WING, True) inputs.set_val(Aircraft.Wing.GLOVE_AND_BAT, 121.05, 'ft**2') # GLOV -inputs.set_val(Aircraft.Wing.INPUT_STATION_DISTRIBUTION, np.array([0.0, 0.5, 1.0])) # ETAW +inputs.set_val(Aircraft.Wing.INPUT_STATION_DISTRIBUTION, np.array([0.5, 1.0])) # ETAW inputs.set_val(Aircraft.Wing.LOAD_DISTRIBUTION_CONTROL, 2.0) # PDIST inputs.set_val(Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, 2.0) # CAM inputs.set_val(Aircraft.Wing.MISC_MASS_SCALER, 1.0) # FRWI3 @@ -364,7 +364,7 @@ outputs.set_val(Aircraft.Wing.ASPECT_RATIO, 3.4488813) # AR outputs.set_val(Aircraft.Wing.ASPECT_RATIO_REFERENCE, 3.4488813) # ARREF outputs.set_val(Aircraft.Wing.MASS, 86742.28126808, 'lbm') # WWING -outputs.set_val(Aircraft.Wing.ROOT_CHORD, 63.96, 'ft') # XLW +outputs.set_val(Aircraft.Wing.ROOT_CHORD, 91.37142857142858, 'ft') # XLOUT outputs.set_val(Aircraft.Wing.AREA, 16555.972297926455, 'ft**2') # SW outputs.set_val(Aircraft.Wing.LOAD_FRACTION, 0.53107166) # PCTL outputs.set_val(Aircraft.Wing.WETTED_AREA, 33816.732336575638, 'ft**2') # SWET(1) diff --git a/aviary/validation_cases/validation_data/test_models/bwb_root_chord1.csv b/aviary/validation_cases/validation_data/test_models/bwb_root_chord1.csv new file mode 100644 index 0000000000..4b7c1ac82e --- /dev/null +++ b/aviary/validation_cases/validation_data/test_models/bwb_root_chord1.csv @@ -0,0 +1,175 @@ +# created 07/15/26 at 18:40 by iordaz +# FLOPS-derived aircraft input deck converted from hwb.flopsin + +# Input Values +aircraft:air_conditioning:mass_scaler,0.98094,unitless +aircraft:anti_icing:mass_scaler,0.53202,unitless +aircraft:apu:mass_scaler,1.02321,unitless +aircraft:avionics:mass_scaler,1.12323,unitless +aircraft:blended_wing_body_design:detailed_wing_provided,True,unitless +aircraft:blended_wing_body_design:max_bay_width,8.05333333333333,ft +aircraft:blended_wing_body_design:max_num_bays,7,unitless +aircraft:blended_wing_body_design:passenger_leading_edge_sweep,70.0,deg +aircraft:blended_wing_body_design:rear_spar_percent_chord_centerline,0.8,unitless +aircraft:blended_wing_body_design:rear_spar_percent_chord_root,0.7,unitless +aircraft:blended_wing_body_design:wing_root_index,1,unitless +aircraft:canard:laminar_flow_lower,0.0,unitless +aircraft:canard:laminar_flow_upper,0.0,unitless +aircraft:canard:mass_scaler,1.0,unitless +aircraft:crew_and_payload:baggage_mass_per_passenger,35.0,lbm +aircraft:crew_and_payload:cabin_crew_mass_scaler,1.0,unitless +aircraft:crew_and_payload:cargo_container_mass,0.0,lbm +aircraft:crew_and_payload:design:num_business_class,20,unitless +aircraft:crew_and_payload:design:num_economy_class,118,unitless +aircraft:crew_and_payload:design:num_first_class,16,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_business,0,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_economy,0,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_first,0,unitless +# aircraft:crew_and_payload:design:seat_pitch_business,0.0,inch +# aircraft:crew_and_payload:design:seat_pitch_economy,0.0,inch +# aircraft:crew_and_payload:design:seat_pitch_first,0.0,inch +aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless +aircraft:crew_and_payload:mass_per_passenger,165.0,lbm +aircraft:crew_and_payload:misc_cargo,0.0,lbm +aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless +aircraft:crew_and_payload:wing_cargo,0.0,lbm +aircraft:design:base_area,0.0,ft**2 +aircraft:design:cruise_mach,0.785,unitless +aircraft:design:empty_mass_margin_scaler,0.01498,unitless +aircraft:design:gross_mass,150000.,lbm +aircraft:design:landing_to_takeoff_mass_ratio,0.7,unitless +aircraft:design:lift_dependent_drag_coeff_factor,0.93,unitless +aircraft:design:mach,0.785,unitless +aircraft:design:range,3500.0,NM +aircraft:design:subsonic_drag_coeff_factor,0.95,unitless +aircraft:design:supersonic_drag_coeff_factor,1.0,unitless +aircraft:design:type,BWB,unitless +aircraft:design:use_alt_mass,False,unitless +aircraft:design:zero_lift_drag_coeff_factor,0.96,unitless +aircraft:electrical:mass_scaler,1.1976,unitless +aircraft:engine:additional_mass_fraction,0.0,unitless +aircraft:engine:constant_fuel_mass_consumption,0.0,lbm/h +aircraft:engine:data_file,aviary/models/engines/turbofan_28k.csv,unitless +aircraft:engine:flight_idle_max_fraction,1.0,unitless +aircraft:engine:flight_idle_min_fraction,0.08,unitless +aircraft:engine:fuel_flow_scaler_constant_term,0.0,unitless +aircraft:engine:fuel_flow_scaler_linear_term,0.0,unitless +aircraft:engine:generate_flight_idle,True,unitless +aircraft:engine:geopotential_alt,False,unitless +aircraft:engine:ignore_negative_thrust,False,unitless +aircraft:engine:mass_scaler,1.15,unitless +aircraft:engine:num_fuselage_engines,2,unitless +aircraft:engine:num_wing_engines,0,unitless +aircraft:engine:reference_mass,6647.8,lbm +aircraft:engine:reference_sls_thrust,35272.6,lbf +aircraft:engine:scale_factor,1.0,unitless +aircraft:engine:scale_mass,false,unitless +aircraft:engine:scaled_sls_thrust,35272.6,lbf +aircraft:engine:subsonic_fuel_flow_scaler,1.0,unitless +aircraft:engine:supersonic_fuel_flow_scaler,1.0,unitless +aircraft:engine:wing_locations,0.0974508650149471,unitless +aircraft:fins:area,0.0,ft**2 +aircraft:fins:mass_scaler,1.0,unitless +aircraft:fins:num_fins,0,unitless +aircraft:fuel:auxiliary_fuel_mass_capacity,0.0,lbm +aircraft:fuel:density,6.7,lbm/galUS +aircraft:fuel:fuel_system_mass_scaler,0.93202,unitless +# aircraft:fuel:fuselage_fuel_mass_capacity,26978.9359229032,lbm +aircraft:fuel:ignore_fuel_capacity_constraint,True,unitless +aircraft:fuel:num_tanks,7,unitless +# aircraft:fuel:total_capacity,0.0,lbm +aircraft:fuel:unusable_fuel_mass_scaler,1.0,unitless +aircraft:fuel:wing_fuel_fraction,0.6883549569366508,unitless +aircraft:fuel:wing_fuel_mass_capacity,55240.1852923682,lbm +aircraft:furnishings:mass_scaler,0.81859,unitless +aircraft:fuselage:height_to_width_ratio,0.088288568890786,unitless +aircraft:fuselage:laminar_flow_lower,0.0,unitless +aircraft:fuselage:laminar_flow_upper,0.0,unitless +aircraft:fuselage:length,91.2160366229902,ft +aircraft:fuselage:mass_scaler,0.707,unitless +aircraft:fuselage:max_height,8.05333333333333,ft +aircraft:fuselage:max_width,40.72,ft +aircraft:fuselage:military_cargo_floor,False,unitless +aircraft:fuselage:num_fuselages,1,unitless +# aircraft:fuselage:passenger_compartment_length,73.1,ft +aircraft:fuselage:sidebody_thickness_to_chord,0.1423,unitless +aircraft:fuselage:simple_layout,True,unitless +aircraft:horizontal_tail:area,0.0,ft**2 +aircraft:horizontal_tail:aspect_ratio,0.0,unitless +aircraft:horizontal_tail:laminar_flow_lower,0.0,unitless +aircraft:horizontal_tail:laminar_flow_upper,0.0,unitless +aircraft:horizontal_tail:mass_scaler,1.42225,unitless +aircraft:horizontal_tail:num_tails,0,unitless +aircraft:horizontal_tail:sweep,0.0,deg +aircraft:horizontal_tail:taper_ratio,0.0,unitless +aircraft:horizontal_tail:thickness_to_chord,0.088288568890786,unitless +aircraft:horizontal_tail:vertical_tail_mount_location,0.0,unitless +aircraft:hydraulics:mass_scaler,0.95543,unitless +aircraft:hydraulics:system_pressure,5000.0,psi +aircraft:instruments:mass_scaler,1.66955,unitless +aircraft:landing_gear:main_gear_mass_scaler,0.8846,unitless +aircraft:landing_gear:main_gear_oleo_length,61.2219219983329,inch +aircraft:landing_gear:nose_gear_mass_scaler,0.8846,unitless +aircraft:landing_gear:nose_gear_oleo_length,68.7849667764527,inch +aircraft:nacelle:avg_diameter,7.47993413959031,ft +aircraft:nacelle:avg_length,36.360790956342,ft +aircraft:nacelle:laminar_flow_lower,0.0,unitless +aircraft:nacelle:laminar_flow_upper,0.0,unitless +aircraft:nacelle:wetted_area,33.672614,ft**2 +aircraft:paint:mass_per_unit_area,0.0,lbm/ft**2 +aircraft:propulsion:engine_oil_mass_scaler,1.0,unitless +aircraft:propulsion:misc_mass_scaler,0.0,unitless +aircraft:vertical_tail:area,30.7942989916162,ft**2 +aircraft:vertical_tail:aspect_ratio,5.11122056846747,unitless +aircraft:vertical_tail:laminar_flow_lower,0.0,unitless +aircraft:vertical_tail:laminar_flow_upper,0.0,unitless +aircraft:vertical_tail:mass_scaler,1.42225,unitless +aircraft:vertical_tail:num_tails,2,unitless +aircraft:vertical_tail:sweep,40.0,deg +aircraft:vertical_tail:taper_ratio,0.333333333333333,unitless +aircraft:vertical_tail:thickness_to_chord,0.12,unitless +aircraft:vertical_tail:wetted_area,65.522389,ft**2 +aircraft:wing:aeroelastic_tailoring_factor,0.33333,unitless +aircraft:wing:airfoil_technology,2.0,unitless +aircraft:wing:aspect_ratio_reference,0.0,unitless +aircraft:wing:bending_material_mass_scaler,1.0,unitless +aircraft:wing:bwb_aftbody_mass_scaler,1.0,unitless +aircraft:wing:chord_per_semispan_distribution,1.07299363828237,0.27377822877684,0.207180266232875,0.157454842780528,0.0484078615253809,0.0345217326821224,unitless +aircraft:wing:composite_fraction,1.0,unitless +aircraft:wing:control_surface_area_ratio,0.298566608695837,unitless +aircraft:wing:detailed_wing,True,unitless +aircraft:wing:dihedral,1.0,deg +aircraft:wing:glove_and_bat,0.0,ft**2 +aircraft:wing:input_station_distribution,0,0.23949901008879,0.294314735080018,0.497582143182209,0.943344002077232,1.0,unitless +aircraft:wing:laminar_flow_lower,0.0,unitless +aircraft:wing:laminar_flow_upper,0.0,unitless +aircraft:wing:load_distribution_control,2.0,unitless +aircraft:wing:load_path_sweep_distribution,0.0,0.0,19.531955656777,19.531955629457,19.5319174438054,deg +aircraft:wing:mass_scaler,0.7425,unitless +aircraft:wing:max_camber_at_70_semispan,0.02,unitless +aircraft:wing:misc_mass_scaler,1.7,unitless +aircraft:wing:num_integration_stations,100,unitless +aircraft:wing:outboard_semispan,65.01078978,ft +aircraft:wing:shear_control_mass_scaler,0.75,unitless +aircraft:wing:span,170.021579566879,ft +aircraft:wing:span_efficiency_factor,1.0,unitless +aircraft:wing:span_efficiency_reduction,False,unitless +aircraft:wing:strut_bracing_factor,0.0,unitless +aircraft:wing:surface_control_mass_scaler,1.77696,unitless +aircraft:wing:sweep,28.289994606366,deg +aircraft:wing:taper_ratio,0.166626548511716,unitless +aircraft:wing:thickness_to_chord,0.088288568890786,unitless +aircraft:wing:thickness_to_chord_distribution,0.088288568890786,0.190913189938669,0.1423,0.1223,0.118,0.10582,unitless +aircraft:wing:thickness_to_chord_reference,0.0,unitless +aircraft:wing:ultimate_load_factor,3.75,unitless +aircraft:wing:var_sweep_mass_penalty,0.0,unitless +aircraft:wing:wetted_area,9111.14415,ft**2 +mission:constraints:max_mach,0.85,unitless +mission:landing:lift_coefficient_max,0.8,unitless +mission:takeoff:braking_friction_coefficient,0.3,unitless +mission:takeoff:fuel_mass,0.0,lbm +mission:takeoff:lift_coefficient_max,0.8,unitless +mission:takeoff:rolling_friction_coefficient,0.025,unitless +settings:aerodynamics_method,FLOPS,unitless +settings:equations_of_motion,energy_state,unitless +settings:mass_method,FLOPS,unitless diff --git a/aviary/validation_cases/validation_data/test_models/bwb_root_chord2.csv b/aviary/validation_cases/validation_data/test_models/bwb_root_chord2.csv new file mode 100644 index 0000000000..2f5c95a591 --- /dev/null +++ b/aviary/validation_cases/validation_data/test_models/bwb_root_chord2.csv @@ -0,0 +1,175 @@ +# created 07/15/26 at 18:40 by iordaz +# FLOPS-derived aircraft input deck converted from hwb.flopsin + +# Input Values +aircraft:air_conditioning:mass_scaler,0.98094,unitless +aircraft:anti_icing:mass_scaler,0.53202,unitless +aircraft:apu:mass_scaler,1.02321,unitless +aircraft:avionics:mass_scaler,1.12323,unitless +aircraft:blended_wing_body_design:detailed_wing_provided,True,unitless +aircraft:blended_wing_body_design:max_bay_width,8.05333333333333,ft +aircraft:blended_wing_body_design:max_num_bays,7,unitless +aircraft:blended_wing_body_design:passenger_leading_edge_sweep,70.0,deg +aircraft:blended_wing_body_design:rear_spar_percent_chord_centerline,0.8,unitless +aircraft:blended_wing_body_design:rear_spar_percent_chord_root,0.7,unitless +aircraft:blended_wing_body_design:wing_root_index,2,unitless +aircraft:canard:laminar_flow_lower,0.0,unitless +aircraft:canard:laminar_flow_upper,0.0,unitless +aircraft:canard:mass_scaler,1.0,unitless +aircraft:crew_and_payload:baggage_mass_per_passenger,35.0,lbm +aircraft:crew_and_payload:cabin_crew_mass_scaler,1.0,unitless +aircraft:crew_and_payload:cargo_container_mass,0.0,lbm +aircraft:crew_and_payload:design:num_business_class,20,unitless +aircraft:crew_and_payload:design:num_economy_class,118,unitless +aircraft:crew_and_payload:design:num_first_class,16,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_business,0,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_economy,0,unitless +# aircraft:crew_and_payload:design:num_seats_abreast_first,0,unitless +# aircraft:crew_and_payload:design:seat_pitch_business,0.0,inch +# aircraft:crew_and_payload:design:seat_pitch_economy,0.0,inch +# aircraft:crew_and_payload:design:seat_pitch_first,0.0,inch +aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless +aircraft:crew_and_payload:mass_per_passenger,165.0,lbm +aircraft:crew_and_payload:misc_cargo,0.0,lbm +aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless +aircraft:crew_and_payload:wing_cargo,0.0,lbm +aircraft:design:base_area,0.0,ft**2 +aircraft:design:cruise_mach,0.785,unitless +aircraft:design:empty_mass_margin_scaler,0.01498,unitless +aircraft:design:gross_mass,155932.,lbm +aircraft:design:landing_to_takeoff_mass_ratio,0.7,unitless +aircraft:design:lift_dependent_drag_coeff_factor,0.93,unitless +aircraft:design:mach,0.785,unitless +aircraft:design:range,3500.0,NM +aircraft:design:subsonic_drag_coeff_factor,0.95,unitless +aircraft:design:supersonic_drag_coeff_factor,1.0,unitless +aircraft:design:type,BWB,unitless +aircraft:design:use_alt_mass,False,unitless +aircraft:design:zero_lift_drag_coeff_factor,0.96,unitless +aircraft:electrical:mass_scaler,1.1976,unitless +aircraft:engine:additional_mass_fraction,0.0,unitless +aircraft:engine:constant_fuel_mass_consumption,0.0,lbm/h +aircraft:engine:data_file,aviary/models/engines/turbofan_28k.csv,unitless +aircraft:engine:flight_idle_max_fraction,1.0,unitless +aircraft:engine:flight_idle_min_fraction,0.08,unitless +aircraft:engine:fuel_flow_scaler_constant_term,0.0,unitless +aircraft:engine:fuel_flow_scaler_linear_term,0.0,unitless +aircraft:engine:generate_flight_idle,True,unitless +aircraft:engine:geopotential_alt,False,unitless +aircraft:engine:ignore_negative_thrust,False,unitless +aircraft:engine:mass_scaler,1.15,unitless +aircraft:engine:num_fuselage_engines,2,unitless +aircraft:engine:num_wing_engines,0,unitless +aircraft:engine:reference_mass,6647.8,lbm +aircraft:engine:reference_sls_thrust,35272.6,lbf +aircraft:engine:scale_factor,1.0,unitless +aircraft:engine:scale_mass,false,unitless +aircraft:engine:scaled_sls_thrust,35272.6,lbf +aircraft:engine:subsonic_fuel_flow_scaler,1.0,unitless +aircraft:engine:supersonic_fuel_flow_scaler,1.0,unitless +aircraft:engine:wing_locations,0.0974508650149471,unitless +aircraft:fins:area,0.0,ft**2 +aircraft:fins:mass_scaler,1.0,unitless +aircraft:fins:num_fins,0,unitless +aircraft:fuel:auxiliary_fuel_mass_capacity,0.0,lbm +aircraft:fuel:density,6.7,lbm/galUS +aircraft:fuel:fuel_system_mass_scaler,0.93202,unitless +# aircraft:fuel:fuselage_fuel_mass_capacity,26978.9359229032,lbm +aircraft:fuel:ignore_fuel_capacity_constraint,True,unitless +aircraft:fuel:num_tanks,7,unitless +# aircraft:fuel:total_capacity,0.0,lbm +aircraft:fuel:unusable_fuel_mass_scaler,1.0,unitless +aircraft:fuel:wing_fuel_fraction,0.6883549569366508,unitless +aircraft:fuel:wing_fuel_mass_capacity,55240.1852923682,lbm +aircraft:furnishings:mass_scaler,0.81859,unitless +aircraft:fuselage:height_to_width_ratio,0.088288568890786,unitless +aircraft:fuselage:laminar_flow_lower,0.0,unitless +aircraft:fuselage:laminar_flow_upper,0.0,unitless +aircraft:fuselage:length,91.2160366229902,ft +aircraft:fuselage:mass_scaler,0.707,unitless +aircraft:fuselage:max_height,8.05333333333333,ft +aircraft:fuselage:max_width,40.72,ft +aircraft:fuselage:military_cargo_floor,False,unitless +aircraft:fuselage:num_fuselages,1,unitless +# aircraft:fuselage:passenger_compartment_length,73.1,ft +aircraft:fuselage:sidebody_thickness_to_chord,0.1423,unitless +aircraft:fuselage:simple_layout,True,unitless +aircraft:horizontal_tail:area,0.0,ft**2 +aircraft:horizontal_tail:aspect_ratio,0.0,unitless +aircraft:horizontal_tail:laminar_flow_lower,0.0,unitless +aircraft:horizontal_tail:laminar_flow_upper,0.0,unitless +aircraft:horizontal_tail:mass_scaler,1.42225,unitless +aircraft:horizontal_tail:num_tails,0,unitless +aircraft:horizontal_tail:sweep,0.0,deg +aircraft:horizontal_tail:taper_ratio,0.0,unitless +aircraft:horizontal_tail:thickness_to_chord,0.088288568890786,unitless +aircraft:horizontal_tail:vertical_tail_mount_location,0.0,unitless +aircraft:hydraulics:mass_scaler,0.95543,unitless +aircraft:hydraulics:system_pressure,5000.0,psi +aircraft:instruments:mass_scaler,1.66955,unitless +aircraft:landing_gear:main_gear_mass_scaler,0.8846,unitless +aircraft:landing_gear:main_gear_oleo_length,61.2219219983329,inch +aircraft:landing_gear:nose_gear_mass_scaler,0.8846,unitless +aircraft:landing_gear:nose_gear_oleo_length,68.7849667764527,inch +aircraft:nacelle:avg_diameter,7.47993413959031,ft +aircraft:nacelle:avg_length,36.360790956342,ft +aircraft:nacelle:laminar_flow_lower,0.0,unitless +aircraft:nacelle:laminar_flow_upper,0.0,unitless +aircraft:nacelle:wetted_area,33.672614,ft**2 +aircraft:paint:mass_per_unit_area,0.0,lbm/ft**2 +aircraft:propulsion:engine_oil_mass_scaler,1.0,unitless +aircraft:propulsion:misc_mass_scaler,0.0,unitless +aircraft:vertical_tail:area,30.7942989916162,ft**2 +aircraft:vertical_tail:aspect_ratio,5.11122056846747,unitless +aircraft:vertical_tail:laminar_flow_lower,0.0,unitless +aircraft:vertical_tail:laminar_flow_upper,0.0,unitless +aircraft:vertical_tail:mass_scaler,1.42225,unitless +aircraft:vertical_tail:num_tails,2,unitless +aircraft:vertical_tail:sweep,40.0,deg +aircraft:vertical_tail:taper_ratio,0.333333333333333,unitless +aircraft:vertical_tail:thickness_to_chord,0.12,unitless +aircraft:vertical_tail:wetted_area,65.522389,ft**2 +aircraft:wing:aeroelastic_tailoring_factor,0.33333,unitless +aircraft:wing:airfoil_technology,2.0,unitless +aircraft:wing:aspect_ratio_reference,0.0,unitless +aircraft:wing:bending_material_mass_scaler,1.0,unitless +aircraft:wing:bwb_aftbody_mass_scaler,1.0,unitless +aircraft:wing:chord_per_semispan_distribution,1.07299363828237,0.55, 0.27377822877684,0.207180266232875,0.157454842780528,0.0484078615253809,0.0345217326821224,unitless +aircraft:wing:composite_fraction,1.0,unitless +aircraft:wing:control_surface_area_ratio,0.298566608695837,unitless +aircraft:wing:detailed_wing,True,unitless +aircraft:wing:dihedral,1.0,deg +aircraft:wing:glove_and_bat,0.0,ft**2 +aircraft:wing:input_station_distribution,0,0.15,0.23949901008879,0.294314735080018,0.497582143182209,0.943344002077232,1.0,unitless +aircraft:wing:laminar_flow_lower,0.0,unitless +aircraft:wing:laminar_flow_upper,0.0,unitless +aircraft:wing:load_distribution_control,2.0,unitless +aircraft:wing:load_path_sweep_distribution,0.0,0.0,0.0,19.531955656777,19.531955629457,19.5319174438054,deg +aircraft:wing:mass_scaler,0.7425,unitless +aircraft:wing:max_camber_at_70_semispan,0.02,unitless +aircraft:wing:misc_mass_scaler,1.7,unitless +aircraft:wing:num_integration_stations,100,unitless +aircraft:wing:outboard_semispan,65.01078978,ft +aircraft:wing:shear_control_mass_scaler,0.75,unitless +aircraft:wing:span,170.021579566879,ft +aircraft:wing:span_efficiency_factor,1.0,unitless +aircraft:wing:span_efficiency_reduction,False,unitless +aircraft:wing:strut_bracing_factor,0.0,unitless +aircraft:wing:surface_control_mass_scaler,1.77696,unitless +aircraft:wing:sweep,28.289994606366,deg +aircraft:wing:taper_ratio,0.166626548511716,unitless +aircraft:wing:thickness_to_chord,0.088288568890786,unitless +aircraft:wing:thickness_to_chord_distribution,0.088288568890786,0.11,0.190913189938669,0.1423,0.1223,0.118,0.10582,unitless +aircraft:wing:thickness_to_chord_reference,0.0,unitless +aircraft:wing:ultimate_load_factor,3.75,unitless +aircraft:wing:var_sweep_mass_penalty,0.0,unitless +aircraft:wing:wetted_area,9111.14415,ft**2 +mission:constraints:max_mach,0.85,unitless +mission:landing:lift_coefficient_max,0.8,unitless +mission:takeoff:braking_friction_coefficient,0.3,unitless +mission:takeoff:fuel_mass,0.0,lbm +mission:takeoff:lift_coefficient_max,0.8,unitless +mission:takeoff:rolling_friction_coefficient,0.025,unitless +settings:aerodynamics_method,FLOPS,unitless +settings:equations_of_motion,energy_state,unitless +settings:mass_method,FLOPS,unitless diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index fd9d1a8cae..a170848188 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -383,6 +383,37 @@ default_value=0.0, ) +add_meta_data( + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_CENTERLINE, + meta_data=_MetaData, + historical_name={'GASP': None, 'FLOPS': 'FUSEIN.RSPCHD'}, + units='unitless', + desc='BWB rear spar percent chord at the centerline. The cabin generally ends here.', + default_value=0.7, +) + +add_meta_data( + Aircraft.BWB.REAR_SPAR_PERCENT_CHORD_ROOT, + meta_data=_MetaData, + historical_name={'GASP': None, 'FLOPS': 'FUSEIN.RSPSOB'}, + units='unitless', + desc='BWB rear spar percent chord at the wing root. The cabin generally ends here.', + default_value=0.7, +) + +add_meta_data( + Aircraft.BWB.WING_ROOT_INDEX, + meta_data=_MetaData, + historical_name={'GASP': None, 'FLOPS': 'FUSEIN.NESOB'}, + desc='When using the detailed wing definition, the index where that defines the location of ' + 'the wing root. When this is 0, the wing definition starts at the root. When it is greater ' + 'than 0, the wing definition should start at the centerline, and the root chord is defined at ' + 'this index. NOTE: index started at 1 in FLOPS, but starts at 0 in Aviary.', + option=True, + types=int, + default_value=0, +) + # _____ _ # / ____| | | # | | __ _ _ __ __ _ _ __ __| | @@ -3143,6 +3174,22 @@ default_value=0.0, ) +add_meta_data( + Aircraft.Fuselage.CABIN_SIDEWALL_LENGTH_MIN, + meta_data=_MetaData, + historical_name={ + 'GASP': None, + 'FLOPS': 'WTIN.XLWMIN', + }, + units='ft', + option=True, + types=float, + desc='Minimum outboard wall of the passenger cabin in a BWB. The default value is based on a ' + 'required maximum depth at the side wall of 8.25 ft divided by a fuselage thickness/chord ' + 'ratio of 0.15 and 70 percent of the resulting wing root chord of 55 ft.', + default_value=38.5, +) + add_meta_data( Aircraft.Fuselage.CHARACTERISTIC_LENGTH, meta_data=_MetaData, @@ -5907,7 +5954,7 @@ meta_data=_MetaData, historical_name={ 'GASP': 'INGASP.CROOTW', - 'FLOPS': 'WTIN.XLW', + 'FLOPS': None, # 'XLOUT', }, units='ft', desc='wing chord length at at the wing/fuselage intersection', diff --git a/aviary/variable_info/variables.py b/aviary/variable_info/variables.py index d2fc4b4e43..afebc56fd2 100644 --- a/aviary/variable_info/variables.py +++ b/aviary/variable_info/variables.py @@ -43,6 +43,13 @@ class BWB: PASSENGER_LEADING_EDGE_SWEEP = ( 'aircraft:blended_wing_body_design:passenger_leading_edge_sweep' ) + REAR_SPAR_PERCENT_CHORD_CENTERLINE = ( + 'aircraft:blended_wing_body_design:rear_spar_percent_chord_centerline' + ) + REAR_SPAR_PERCENT_CHORD_ROOT = ( + 'aircraft:blended_wing_body_design:rear_spar_percent_chord_root' + ) + WING_ROOT_INDEX = 'aircraft:blended_wing_body_design:wing_root_index' class Canard: AREA = 'aircraft:canard:area' @@ -294,6 +301,7 @@ class Fuselage: AISLE_WIDTH = 'aircraft:fuselage:aisle_width' AVG_DIAMETER = 'aircraft:fuselage:avg_diameter' CABIN_AREA = 'aircraft:fuselage:cabin_area' + CABIN_SIDEWALL_LENGTH_MIN = 'aircraft:fuselage:cabin_sidewall_length_min' CHARACTERISTIC_LENGTH = 'aircraft:fuselage:characteristic_length' CROSS_SECTION = 'aircraft:fuselage:cross_section' DELTA_DIAMETER = 'aircraft:fuselage:delta_diameter'