From 72bb69faae6fea179020c395019c36bedcd94507 Mon Sep 17 00:00:00 2001 From: bdphilli Date: Sat, 28 Mar 2026 13:08:10 -0400 Subject: [PATCH] Set aircraft-derived upper bounds on mass and distance states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When mass_bounds or distance_bounds have None as the upper bound (the default), Dymos sets it to ~1e21. With mass_ref=1e4 and distance_ref=1e6, this creates scaled upper bounds of ~1e17 and ~1e15 respectively, while other design variables have scaled ranges of O(1). In build_phase(), replace None upper bounds with values derived from aviary_inputs: 2x GROSS_MASS for mass, 2.5x DESIGN_RANGE for distance. This reduces scaled upper bounds to O(10), matching the scale of other design variables. User-specified bounds are preserved — the override only applies when the upper bound is None. Addresses #607 --- aviary/mission/flight_phase_builder.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/aviary/mission/flight_phase_builder.py b/aviary/mission/flight_phase_builder.py index 01f1beeccd..38b5d6dfab 100644 --- a/aviary/mission/flight_phase_builder.py +++ b/aviary/mission/flight_phase_builder.py @@ -15,7 +15,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.enums import EquationsOfMotion, ThrottleAllocation, Transcription from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Aircraft, Dynamic +from aviary.variable_info.variables import Aircraft, Dynamic, Mission # Height Energy and Solved2DOF use this builder @@ -220,6 +220,23 @@ def build_phase( ############## # Add States # ############## + + # Replace None upper bounds with aircraft-derived values so that + # Dymos doesn't set them to ~1e21, which creates scaled upper bounds + # of ~1e17 (mass) and ~1e15 (distance) and wrecks SNOPT conditioning. + # See https://github.com/OpenMDAO/Aviary/issues/607 + if aviary_options is not None: + gross_mass = aviary_options.get_val(Mission.Design.GROSS_MASS, units='kg') + design_range = aviary_options.get_val(Mission.Design.RANGE, units='m') + + mass_bounds, mass_units = user_options['mass_bounds'] + if mass_bounds[1] is None: + user_options['mass_bounds'] = ((mass_bounds[0], 2.0 * gross_mass), mass_units) + + dist_bounds, dist_units = user_options['distance_bounds'] + if dist_bounds[1] is None: + user_options['distance_bounds'] = ((dist_bounds[0], 2.5 * design_range), dist_units) + if phase_type is EquationsOfMotion.HEIGHT_ENERGY: rate_source = Dynamic.Vehicle.Propulsion.FUEL_FLOW_RATE_NEGATIVE_TOTAL else: