Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
35649ca
add experiment mission tests
xjjiang Jan 23, 2026
70d18ef
add settings:verbosity to BWB csv files
xjjiang Jan 23, 2026
3018e3e
work in progress
xjjiang Jan 23, 2026
61c1d6d
check divided by zero in ground_effect.py
xjjiang Jan 24, 2026
eead582
add COMPUTED_CORE_INPUTS_BWB
xjjiang Jan 24, 2026
5359290
deal with BWB where there is no horizontal tails
xjjiang Jan 24, 2026
f12b7ba
Merge branch 'OpenMDAO:main' into BWB_FLOPS_mission
xjjiang Jan 24, 2026
724faa8
Merge branch 'BWB_FLOPS_mission' of github.com:xjjiang/om-Aviary into…
xjjiang Jan 24, 2026
4ec92d9
post mission require aircraft:wing:area. If it is not available, do n…
xjjiang Jan 28, 2026
67001d8
work in progress.
xjjiang Jan 28, 2026
ec1b682
if Aircraft.Design.TYPE is not in input, assume default AircraftTypes…
xjjiang Jan 28, 2026
840f3cf
add TODO. It is a question
xjjiang Jan 28, 2026
66925c2
remove mission:design:lift_coefficient from csv file
xjjiang Jan 28, 2026
88f22b7
work in progress
xjjiang Jan 28, 2026
d87758e
smooth out int function
xjjiang Jan 29, 2026
59de74b
roll back
xjjiang Jan 29, 2026
849f7ad
lower tolerance for Aircraft.BWB.NUM_BAYS
xjjiang Jan 29, 2026
dedfd72
modify the computation of num_bays
xjjiang Jan 29, 2026
858c470
modify function smooth_int_tanh()
xjjiang Jan 29, 2026
4387402
remove test_bwb_Experiment_FwFm_2.py
xjjiang Jan 29, 2026
27a3972
rename test_bwb_Experiment_FwFm_1.py to test_bwb_FwFm.py
xjjiang Jan 29, 2026
fcb6723
roll back ground_effect.py
xjjiang Jan 30, 2026
57df4f5
minor update
xjjiang Jan 30, 2026
e5507fd
merge Ken's work
xjjiang Feb 2, 2026
113557d
Merge branch 'main' into BWB_FLOPS_mission
xjjiang Feb 5, 2026
f58866a
Merge branch 'main' into BWB_FLOPS_mission
xjjiang Feb 6, 2026
f0a3e71
Merge branch 'BWB_FLOPS_F2A' into BWB_FLOPS_mission
xjjiang Feb 7, 2026
3fe0e0c
Merge branch 'OpenMDAO:main' into BWB_FLOPS_mission
xjjiang Feb 17, 2026
2120be5
make Aircraft.Fuselage.SIMPLE_LAYOUT a boolean
xjjiang Feb 19, 2026
f86f537
Merge branch 'main' into BWB_FLOPS_mission
xjjiang Feb 19, 2026
6200ef4
fixed a typo
xjjiang Feb 19, 2026
db78b32
Merge branch 'main' into BWB_FLOPS_mission
xjjiang Feb 20, 2026
c67a09a
Merge branch 'OpenMDAO:main' into BWB_FLOPS_mission
xjjiang Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions aviary/models/aircraft/blended_wing_body/bwb_simple_FLOPS.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 44 additions & 2 deletions aviary/subsystems/aerodynamics/aerodynamics_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions aviary/subsystems/geometry/flops_based/canard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
21 changes: 12 additions & 9 deletions aviary/subsystems/geometry/flops_based/fuselage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion aviary/subsystems/test/test_flops_based_premission.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions aviary/utils/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
152 changes: 152 additions & 0 deletions aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions aviary/variable_info/variable_meta_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading