Running a Custom Optimization#
Aviary’s Python API allows for significantly more customization over what Aviary does. In this example, we will dig further into the Python API to optimize an aircraft with new design variables and constraints.
The Optimization Problem#
In this example, we have three design variables we are varying to size an aircraft’s wings and engines:
Engine scale factor, which will adjust how much thrust our engines are sized to output
Wing area
Wing span
We also have one constraint limiting our aircraft’s design:
Aspect ratio less than or equal to 12
This will limit the optimizer’s choice of design variables related to wing area. Currently, there is no constraint directly affecting engine size, so the optimizer is free to select any value within the bounds we will specify. The max aspect ratio was arbitrarily chosen for this example, but could represent some specific structural requirement or other limitation.
We will be using Aviary’s default objective function, which minimizes fuel burn over the design mission.
Setting Up Aviary#
For this example we cannot use the run_mission command or the run_aviary() function in Python. The tradeoff of the “single-line” simplicity of those methods is a lack of customizability over how the problem is set up, which is needed for this example. Instead we will go one level deeper into Aviary’s Python API and directly utilize the AviaryProblem object. In brief, an AviaryProblem is an OpenMDAO Problem that is extended to have additional Aviary-specific features. Creating and running an AviaryProblem is very similar to the steps needed to use a pure OpenMDAO problem.
import aviary.api as av
from aviary.models.missions.energy_state_default import phase_info
# Suppress outputs by setting verbosity as zero (quiet mode)
prob = av.AviaryProblem(verbosity=0)
# update climb phase time bounds so trajectory isn't unnecessarily constrained:
phase_info['climb']['user_options']['time_duration_bounds'] = ((20.0, 192.0), 'min')
phase_info['cruise']['user_options']['time_initial_bounds'] = ((20.0, 192.0), 'min')
# Load aircraft and options data from provided sources
prob.load_inputs(
'models/aircraft/advanced_single_aisle/advanced_single_aisle_FLOPS.csv', phase_info
)
# delete overrides that will be impacted by design variables
prob.aviary_inputs.delete(av.Aircraft.Wing.ASPECT_RATIO)
prob.aviary_inputs.delete(av.Aircraft.Wing.WETTED_AREA)
prob.aviary_inputs.delete(av.Aircraft.Fuselage.WETTED_AREA)
# Sanity check inputs and guess initial conditions for mission phases
prob.check_and_preprocess_inputs()
# Have Aviary build the OpenMDAO model with pre-mission, mission, and post-mission components
prob.build_model()
# Selecting optimizer and iteration limit are optional
prob.add_driver('SLSQP', max_iter=25)
# Add the default design variables needed to size the aircraft
prob.add_design_variables()
# Add wing area, span and engine scaling as additional design variables
prob.model.add_design_var(av.Aircraft.Engine.SCALE_FACTOR, lower=0.5, upper=1.5, ref=1)
prob.model.add_design_var(av.Aircraft.Wing.AREA, lower=1000, upper=1800, units='ft**2', ref=1400)
# upper value of span chosen as max for group C gate width.
prob.model.add_design_var(av.Aircraft.Wing.SPAN, lower=90, upper=118, units='ft', ref=100)
# Add the default objective function (minimum fuel burn)
prob.add_objective()
# Add constraint for wing aspect ratio so it remains 'reasonable'. Any model output can be constrained in this way.
prob.model.add_constraint(av.Aircraft.Wing.ASPECT_RATIO, upper=12.0, ref=10.0)
# Standard OpenMDAO problem setup step
prob.setup()
# Run the optimization problem
prob.run_aviary_problem()
This code mirrors what run_aviary() is actually doing behind the scenes - these steps can be broken down even further, but that isn’t necessary for this example.
There is a combination of AviaryProblem-specific methods and some standard OpenMDAO ones mixed together here. add_design_var() and add_constraint() are two OpenMDAO methods used to define our optimization problem. Because the AviaryProblem is based on the OpenMDAO Problem, we can directly use any method that exists for the OpenMDAO Problem too.
The order these steps are executed in is very important! Flipping around the order will break Aviary and result in an immediate error or important parts of the problem definition not being done correctly. The exception is our custom optimization problem setup steps (adding design variables and constraints). The location where we define them is less restrictive - as long as it is done between build_model() and setup() the model will work. In general, when customizing an AviaryProblem it is good practice to do any modifications in the same place you would make similar changes to a pure OpenMDAO problem. This takes some familiarity with OpenMDAO to master, but the reward is extremely broad control over exactly what optimization problem Aviary runs.
Now let’s take a look at some results of the problem:
Aircraft Sizing and Mission Performance
---------------
Mission Zero Fuel Mass = 98702.89 lbm
Mission Fuel Mass = 12668.98 lbm
Takeoff Gross Weight = 111371.87 lbm
Design Variables
---------------
Wing Area (started at 1220) = 1160.33 ft^2
Wing Span (started at 118.75) = 118.0 ft
Engine Scale Factor (started at 1) = 0.762
Constraints
---------------
Wing Aspect Ratio = 12.0
We can see that the value for all our design variables have changed from their initial values, which is expected since they were design variables for the problem. In addition, the value for our new aspect ratio constraint is right against the upper specified bound, which is expected. The aircraft should want a higher aspect ratio wing for the better aerodynamic efficiency, as long as it is not more strongly penalized for doing so elsewhere.
Adding a mission performance constraint#
The earlier example shows how to add a constraint on an output variable. In the next example we will add to the above problem and also constrain an output from the EOM (equations of motion) for a specific phase. In this case we will add a climb rate constraint for the cruise phase. This is a typical constraint to add for conceptual design to ensure that the aircraft has sufficient performance margin during cruise. In this example case, ensuring the aircraft has a high amount of available excess power should force the engines to be larger.
The more general form for any output from the EOM is this:
phase_info['cruise']['user_options']['constraints'] = { av.Dynamic.Mission.ALTITUDE_RATE_MAX: { 'lower': 600, 'type': 'path', 'units': 'ft/min', } }
Since this is a common constraint in aircraft design, for this variable there is a special option in phase info for adding it:
phase_info['cruise']['user_options']['required_available_climb_rate'] = (600,'ft/min')
Both methods do exactly the same thing in this case. For typical transport aircraft, a lower limit of 600 ft/min is extremely high. Most aircraft of this class can easily achieve a more modest climb rate limit, so we intentionally set it very high here to force the constraint to be active and noticeably change our aircraft design.
Now let’s run a 2nd problem with this constraint included, and look at the results.
prob2 = av.AviaryProblem(verbosity=0)
# modify the phase info by adding a constraint to the phase.
phase_info['cruise']['user_options']['constraints'] = {
av.Dynamic.Mission.ALTITUDE_RATE_MAX: {
'lower': 600,
'type': 'path',
'units': 'ft/min',
'ref': 600.0,
}
}
# Load aircraft and options data from provided sources
prob2.load_inputs(
'models/aircraft/advanced_single_aisle/advanced_single_aisle_FLOPS.csv', phase_info
)
# delete overrides that will be impacted by design variables
prob2.aviary_inputs.delete(av.Aircraft.Wing.ASPECT_RATIO)
prob2.aviary_inputs.delete(av.Aircraft.Wing.WETTED_AREA)
prob2.aviary_inputs.delete(av.Aircraft.Fuselage.WETTED_AREA)
# Sanity check inputs and guess initial conditions for mission phases
prob2.check_and_preprocess_inputs()
# Have Aviary build the OpenMDAO model with pre-mission, mission, and post-mission components
prob2.build_model()
# Selecting optimizer and iteration limit are optional
prob2.add_driver('SLSQP', max_iter=75)
# Add the default design variables needed to size the aircraft
prob2.add_design_variables()
# Add wing area, span and engine scaling as additional design variables
prob2.model.add_design_var(av.Aircraft.Wing.AREA, lower=1000, upper=1800, units='ft**2', ref=1400)
# upper value of span chosen as max for group C gate width.
prob2.model.add_design_var(av.Aircraft.Wing.SPAN, lower=90, upper=119.0, units='ft', ref=100)
prob2.model.add_design_var(av.Aircraft.Engine.SCALE_FACTOR, lower=0.5, upper=1.5, ref=1)
# Add the default objective function (minimum fuel burn)
prob2.add_objective()
# Add constraints for wing aspect ratio so it remains 'reasonable'. Any model output can be constrained in this way.
prob2.model.add_constraint(av.Aircraft.Wing.ASPECT_RATIO, upper=12.0, ref=10.0)
# Standard OpenMDAO problem setup step
prob2.setup()
# Run the optimization problem
prob2.run_aviary_problem()
Aircraft Sizing and Mission Performance
---------------
Mission Zero Fuel Mass = 98805.97 lbm
Mission Fuel Mass = 12669.27 lbm
Takeoff Gross Weight = 111475.25 lbm
Design Variables
---------------
Wing Area (started at 1220) = 1159.85 ft^2
Wing Span (started at 118.75) = 117.98 ft
Engine Scale Factor (started at 1) = 0.769
Constraints
---------------
Wing Aspect Ratio = 12.0
Cruise Altitude_Rate_Max = [652.04, 650.32, 647.88, 647.1, 647.1, 643.33, 637.86, 636.07, 636.07, 631.02, 623.68, 621.26, 621.26, 616.47, 609.58, 607.33, 607.33, 604.77, 601.16, 600.0]
There is an additional printout here of the ALTITUDE_RATE_MAX output for every node in the cruise phase of the trajectory.
We can see the new climb rate constraint has kept the maximum climb rate above the lower bound for every point in cruise. We can also see that the engine scale factor has increased to make the higher climb rate possible.
The Aviary dashboard is a great tool to visualize the trajectory and view the values of design variables and constraints during the various phases of the mission. In general, it is recommended to use the dashboard rather than manually print out individual variables of interest like this example showcased.