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 a4c23481e..a71210190 100644 --- a/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv +++ b/aviary/models/aircraft/blended_wing_body/bwb_detailed_FLOPS.csv @@ -41,6 +41,12 @@ aircraft:design:empty_mass_margin_scaler,0.0,unitless aircraft:design:landing_to_takeoff_mass_ratio,0.8,unitless aircraft:design:lift_dependent_drag_coeff_factor,1.0,unitless aircraft:design:subsonic_drag_coeff_factor,1.0,unitless + +# TODO: Needed to reduce the drag so that engine wasn't full throttle in cruise. +# There must be something about drag that still isn't working for BWB. +aircraft:design:subsonic_drag_coeff_factor,0.7,unitless +# aircraft:design:subsonic_drag_coeff_factor,1.0,unitless + aircraft:design:supersonic_drag_coeff_factor,1.0,unitless aircraft:design:type,BWB,unitless aircraft:design:use_alt_mass,False,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 2cee8859e..11d956315 100644 --- a/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv +++ b/aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv @@ -41,6 +41,12 @@ aircraft:design:empty_mass_margin_scaler,0.0,unitless aircraft:design:landing_to_takeoff_mass_ratio,0.8,unitless aircraft:design:lift_dependent_drag_coeff_factor,1.0,unitless aircraft:design:subsonic_drag_coeff_factor,1.0,unitless + +# TODO: Needed to reduce the drag so that engine wasn't full throttle in cruise. +# There must be something about drag that still isn't working for BWB. +aircraft:design:subsonic_drag_coeff_factor,0.7,unitless +# aircraft:design:subsonic_drag_coeff_factor,1.0,unitless + aircraft:design:supersonic_drag_coeff_factor,1.0,unitless aircraft:design:type,BWB,unitless aircraft:design:use_alt_mass,False,unitless diff --git a/aviary/subsystems/aerodynamics/aerodynamics_builder.py b/aviary/subsystems/aerodynamics/aerodynamics_builder.py index e5167ffc9..0172d7a43 100644 --- a/aviary/subsystems/aerodynamics/aerodynamics_builder.py +++ b/aviary/subsystems/aerodynamics/aerodynamics_builder.py @@ -494,7 +494,15 @@ def get_parameters(self, aviary_inputs=None, **kwargs): params[Aircraft.Design.LIFT_DEPENDENT_DRAG_POLAR] = opts if method == 'computed': - for var in COMPUTED_CORE_INPUTS: + try: + design_type = aviary_inputs.get_val(Aircraft.Design.TYPE) + except: + design_type = AircraftTypes.TRANSPORT + if design_type is AircraftTypes.BLENDED_WING_BODY: + core_inputs_computed = COMPUTED_CORE_INPUTS_BWB + else: + core_inputs_computed = COMPUTED_CORE_INPUTS + for var in core_inputs_computed: meta = _MetaData[var] val = meta['default_value'] @@ -622,7 +630,10 @@ def get_parameters(self, aviary_inputs=None, **kwargs): 'tabular_cruise, low_speed, tabular_low_speed)' ) - design_type = aviary_inputs.get_val(Aircraft.Design.TYPE) + try: + design_type = aviary_inputs.get_val(Aircraft.Design.TYPE) + except: + design_type = AircraftTypes.TRANSPORT if design_type is AircraftTypes.BLENDED_WING_BODY: all_vars.add(Aircraft.Fuselage.LIFT_CURVE_SLOPE_MACH0) @@ -729,6 +740,37 @@ def report(self, prob, reports_folder, **kwargs): Mission.Design.MACH, ] +COMPUTED_CORE_INPUTS_BWB = [ + Aircraft.Design.BASE_AREA, + Aircraft.Design.LIFT_DEPENDENT_DRAG_COEFF_FACTOR, + Aircraft.Design.SUBSONIC_DRAG_COEFF_FACTOR, + Aircraft.Design.SUPERSONIC_DRAG_COEFF_FACTOR, + Aircraft.Design.ZERO_LIFT_DRAG_COEFF_FACTOR, + Aircraft.Fuselage.CHARACTERISTIC_LENGTH, + Aircraft.Fuselage.CROSS_SECTION, + Aircraft.Fuselage.DIAMETER_TO_WING_SPAN, + Aircraft.Fuselage.FINENESS, + Aircraft.Fuselage.LAMINAR_FLOW_LOWER, + Aircraft.Fuselage.LAMINAR_FLOW_UPPER, + Aircraft.Fuselage.LENGTH_TO_DIAMETER, + Aircraft.Fuselage.WETTED_AREA, + Aircraft.Wing.AREA, + Aircraft.Wing.ASPECT_RATIO, + Aircraft.Wing.CHARACTERISTIC_LENGTH, + Aircraft.Wing.FINENESS, + Aircraft.Wing.LAMINAR_FLOW_LOWER, + Aircraft.Wing.LAMINAR_FLOW_UPPER, + Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, + Aircraft.Wing.SPAN_EFFICIENCY_FACTOR, + Aircraft.Wing.SWEEP, + Aircraft.Wing.TAPER_RATIO, + Aircraft.Wing.THICKNESS_TO_CHORD, + Aircraft.Wing.WETTED_AREA, + # Mission.Summary.GROSS_MASS, + Mission.Design.LIFT_COEFFICIENT, + Mission.Design.MACH, +] + TABULAR_CORE_INPUTS = [ Aircraft.Wing.AREA, Aircraft.Design.SUBSONIC_DRAG_COEFF_FACTOR, diff --git a/aviary/subsystems/geometry/flops_based/canard.py b/aviary/subsystems/geometry/flops_based/canard.py index 40095d929..46bee4a1f 100644 --- a/aviary/subsystems/geometry/flops_based/canard.py +++ b/aviary/subsystems/geometry/flops_based/canard.py @@ -12,6 +12,7 @@ class Canard(om.ExplicitComponent): """Calculate the wetted area of canard.""" + # TODO: what is it for? def initialize(self): self.options.declare( 'aviary_options', diff --git a/aviary/subsystems/geometry/flops_based/fuselage.py b/aviary/subsystems/geometry/flops_based/fuselage.py index bab2ecd8f..583e6514b 100644 --- a/aviary/subsystems/geometry/flops_based/fuselage.py +++ b/aviary/subsystems/geometry/flops_based/fuselage.py @@ -477,8 +477,8 @@ def compute(self, inputs, outputs): # Enforce maximum number of bays num_bays_max = self.options[Aircraft.BWB.MAX_NUM_BAYS] - num_bays = int(0.5 + max_width.real / bay_width_max.real) - if num_bays > num_bays_max and num_bays_max > 0: + num_bays = int(0.5 + max_width / bay_width_max) + 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) @@ -654,27 +654,30 @@ def compute(self, inputs, outputs): pax_compart_length = root_chord + tan_sweep * max_width / 2.0 # Enforce maximum number of bays - z = 0.5 + max_width / bay_width_max - z = z[0] - num_bays = int(z.real) - if num_bays > num_bays_max and num_bays_max > 0: + num_bays_tmp = 0.5 + max_width / bay_width_max + if num_bays_tmp[0].real > num_bays_max and num_bays_max > 0: num_bays = num_bays_max + else: + num_bays = int(num_bays_tmp[0].real) # Enforce maximum bay width bay_width = max_width / num_bays if bay_width > bay_width_max: bay_width = bay_width_max - num_bays = int(0.999 + max_width / bay_width) - if num_bays > num_bays_max and num_bays_max > 0: + num_bays_tmp = 0.999 + max_width / bay_width + if num_bays_tmp.real > num_bays_max and num_bays_max > 0: num_bays = num_bays_max max_width = bay_width_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 + else: + num_bays = smooth_int_tanh(num_bays_tmp, mu=40.0) + # If number of bays has changed, recalculate cabin area length = pax_compart_length / rear_spar_percent_chord max_height = height_to_width * length - outputs[Aircraft.BWB.NUM_BAYS] = smooth_int_tanh(num_bays, mu=20.0) + outputs[Aircraft.BWB.NUM_BAYS] = num_bays outputs[Aircraft.Fuselage.LENGTH] = length outputs[Aircraft.Fuselage.PASSENGER_COMPARTMENT_LENGTH] = pax_compart_length diff --git a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py index ee50e0206..f050502e9 100644 --- a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py +++ b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py @@ -397,7 +397,7 @@ def compute(self, inputs, outputs): # This part is repeated in _BWBWing() num_inp_stations = len(self.options[Aircraft.Wing.INPUT_STATION_DIST]) bwb_input_station_dist = np.array( - self.options[Aircraft.Wing.INPUT_STATION_DIST], dtype=float + self.options[Aircraft.Wing.INPUT_STATION_DIST], dtype=width.dtype ) bwb_input_station_dist = np.where( bwb_input_station_dist <= 1.0, diff --git a/aviary/subsystems/test/test_flops_based_premission.py b/aviary/subsystems/test/test_flops_based_premission.py index fee07ac43..a757c2f96 100644 --- a/aviary/subsystems/test/test_flops_based_premission.py +++ b/aviary/subsystems/test/test_flops_based_premission.py @@ -507,7 +507,7 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Wing.ROOT_CHORD], 63.96, 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, tol) + assert_near_equal(prob[Aircraft.BWB.NUM_BAYS], 5.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) diff --git a/aviary/utils/math.py b/aviary/utils/math.py index 5c01de748..378184e98 100644 --- a/aviary/utils/math.py +++ b/aviary/utils/math.py @@ -215,7 +215,7 @@ def smooth_int_tanh(x, mu=10.0): """ Smooth approximation of int(x) using tanh. """ - f = np.floor(x) + f = np.floor(x.real) + x.imag * 1j frac = x - f t = np.tanh(mu * (frac - 0.5)) s = 0.5 * (t + 1) @@ -228,7 +228,7 @@ def d_smooth_int_tanh(x, mu=10.0): Smooth approximation of int(x) using tanh. Returns (y, dy_dx). """ - f = np.floor(x) + f = np.floor(x) + x.imag * 1j frac = x - f t = np.tanh(mu * (frac - 0.5)) dy_dx = 0.5 * mu * (1 - t**2) diff --git a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py new file mode 100644 index 000000000..b5d7be0e0 --- /dev/null +++ b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py @@ -0,0 +1,152 @@ +import unittest +from copy import deepcopy + +from openmdao.core.problem import _clear_problem_names +from openmdao.utils.assert_utils import assert_near_equal +from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs + +from aviary.interface.methods_for_level1 import run_aviary +from aviary.variable_info.variables import Mission + +phase_info = { + 'pre_mission': {'include_takeoff': False, 'optimize_mass': True}, + 'climb': { + 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, + 'user_options': { + 'num_segments': 5, + 'order': 3, + 'mach_optimize': True, + 'mach_initial': (0.3, 'unitless'), + 'mach_bounds': ((0.3, 0.85), 'unitless'), + 'altitude_optimize': True, + 'altitude_initial': (500.0, 'ft'), + 'altitude_bounds': ((500.0, 35000.0), 'ft'), + 'no_descent': True, + 'mass_ref': (875000, 'lbm'), + 'throttle_enforcement': 'control', + 'time_initial_bounds': ((0.0, 0.0), 'min'), + 'time_duration_bounds': ((24.0, 90.0), 'min'), + }, + 'initial_guesses': { + 'altitude': ([500.5, 35000.0], 'ft'), + 'mach': ([0.3, 0.85], 'unitless'), + }, + }, + 'cruise': { + 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, + 'user_options': { + 'num_segments': 5, + 'order': 3, + 'mach_optimize': False, + 'mach_initial': (0.85, 'unitless'), + 'mach_bounds': ((0.85, 0.85), 'unitless'), + 'altitude_optimize': False, + 'altitude_initial': (35000.0, 'ft'), + 'altitude_bounds': ((35000.0, 43000.0), 'ft'), + 'mass_ref': (875000, 'lbm'), + 'distance_ref': (7750, 'nmi'), + 'throttle_enforcement': 'control', + 'time_initial_bounds': ((24.0, 180.0), 'min'), + 'time_duration_bounds': ((10.0, 19.0), 'h'), + }, + 'initial_guesses': { + 'altitude': ([35000, 42000.0], 'ft'), + 'mach': ([0.85, 0.85], 'unitless'), + }, + }, + 'descent': { + 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, + 'user_options': { + 'num_segments': 5, + 'order': 3, + 'mach_optimize': True, + 'mach_initial': (0.85, 'unitless'), + 'mach_final': (0.2, 'unitless'), + 'mach_bounds': ((0.2, 0.85), 'unitless'), + 'altitude_optimize': True, + 'altitude_initial': (42000.0, 'ft'), + 'altitude_final': (500.0, 'ft'), + 'altitude_bounds': ((500.0, 42000.0), 'ft'), + 'mass_ref': (875000, 'lbm'), + 'distance_ref': (7750, 'nmi'), + 'no_climb': True, + 'throttle_enforcement': 'control', + 'time_initial_bounds': ((10, 19.0), 'h'), + 'time_duration_bounds': ((0.15, 1.0), 'h'), + }, + }, + 'post_mission': { + 'include_landing': False, + 'constrain_range': True, + 'target_range': (7750.0, 'nmi'), + }, +} + + +# @use_tempdirs +class BWBProblemPhaseTestCase(unittest.TestCase): + """ + Test the setup and run of a BWB aircraft using FLOPS mass and aero method + and HEIGHT_ENERGY mission method. Expected outputs based on + 'models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv' model. + """ + + def setUp(self): + _clear_problem_names() # need to reset these to simulate separate runs + + @require_pyoptsparse(optimizer='SNOPT') + def test_bench_bwb_FwFm_SNOPT(self): + local_phase_info = deepcopy(phase_info) + prob = run_aviary( + 'models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv', + local_phase_info, + optimizer='SNOPT', + verbosity=1, + max_iter=60, + ) + # prob.model.list_vars(units=True, print_arrays=True) + # prob.list_indep_vars() + # prob.list_problem_vars() + # prob.model.list_outputs() + + rtol = 1e-3 + + # There are no truth values for these. + assert_near_equal( + prob.get_val(Mission.Design.GROSS_MASS, units='lbm'), + 139803.667415, + tolerance=rtol, + ) + + assert_near_equal( + prob.get_val(Mission.Summary.OPERATING_MASS, units='lbm'), + 79873.05255347, + tolerance=rtol, + ) + + assert_near_equal( + prob.get_val(Mission.Summary.TOTAL_FUEL_MASS, units='lbm'), + 26180.61486153, + tolerance=rtol, + ) + + assert_near_equal(prob.get_val(Mission.Summary.RANGE, units='NM'), 3500.0, tolerance=rtol) + + assert_near_equal( + prob.get_val(Mission.Landing.GROUND_DISTANCE, units='ft'), + 2216.0066613, + tolerance=rtol, + ) + + assert_near_equal( + prob.get_val(Mission.Landing.TOUCHDOWN_MASS, units='lbm'), + 116003.31044998, + tolerance=rtol, + ) + + +if __name__ == '__main__': + # unittest.main() + test = BWBProblemPhaseTestCase() + test.setUp() + test.test_bench_bwb_FwFm_SNOPT() diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index e6ffb85e5..cf7771e20 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -3698,6 +3698,7 @@ Aircraft.Fuselage.SIMPLE_LAYOUT, meta_data=_MetaData, historical_name={'GASP': None, 'FLOPS': None, 'LEAPS1': None}, + types=bool, units='unitless', desc='carry out simple or detailed layout of fuselage (for FLOPS based geometry).', option=True,