Optimizing the Mission Profile of a Conventional Aircraft#

Building upon our previous example, this notebook introduces more complexity into the Aviary optimization process. Please see the simple mission example if you haven’t already.

Increasing Complexity in Phase Information#

We will now modify the phase_info object from our prior example by increasing num_segments to 3 and setting mach_optimize to True in each of the three phases. This means that we’ll query the aircraft performance at more points along the mission and also give the optimizer the freedom to choose an optimal Mach profile.

Note

We are still using a mach_polynomial_order and altitude_polynomial_order of 1, which means that the optimal Mach profiles for each phase will be linear (straight lines). Later in this example, we increase this order which will allow the optimizer to choose a more complex Mach profile.

phase_info = {
    'pre_mission': {'include_takeoff': False, 'optimize_mass': True},
    'climb_1': {
        'subsystem_options': {'aerodynamics': {'method': 'computed'}},
        'user_options': {
            'num_segments': 3,
            'order': 3,
            'distance_solve_segments': False,
            'mach_optimize': True,
            'mach_polynomial_order': 1,
            'mach_initial': (0.2, 'unitless'),
            'mach_final': (0.72, 'unitless'),
            'mach_bounds': ((0.18, 0.74), 'unitless'),
            'altitude_optimize': False,
            'altitude_polynomial_order': 1,
            'altitude_initial': (0.0, 'ft'),
            'altitude_final': (30500.0, 'ft'),
            'altitude_bounds': ((0.0, 31000.0), 'ft'),
            'throttle_enforcement': 'path_constraint',
            'time_initial_bounds': ((0.0, 0.0), 'min'),
            'time_duration_bounds': ((27.0, 81.0), 'min'),
        },
        'initial_guesses': {'time': ([0, 54], 'min')},
    },
    'cruise': {
        'subsystem_options': {'aerodynamics': {'method': 'computed'}},
        'user_options': {
            'num_segments': 3,
            'order': 3,
            'mach_optimize': True,
            'mach_polynomial_order': 1,
            'mach_initial': (0.72, 'unitless'),
            'mach_final': (0.72, 'unitless'),
            'mach_bounds': ((0.7, 0.74), 'unitless'),
            'altitude_optimize': False,
            'altitude_initial': (30500.0, 'ft'),
            'altitude_final': (31000.0, 'ft'),
            'altitude_bounds': ((30000.0, 31500.0), 'ft'),
            'throttle_enforcement': 'boundary_constraint',
            'time_initial_bounds': ((27.0, 81.0), 'min'),
            'time_duration_bounds': ((85.5, 256.5), 'min'),
        },
        'initial_guesses': {'time': ([54, 171], 'min')},
    },
    'descent_1': {
        'subsystem_options': {'aerodynamics': {'method': 'computed'}},
        'user_options': {
            'num_segments': 3,
            'order': 3,
            'mach_optimize': True,
            'mach_polynomial_order': 1,
            'mach_initial': (0.72, 'unitless'),
            'mach_final': (0.2, 'unitless'),
            'mach_bounds': ((0.18, 0.74), 'unitless'),
            'altitude_optimize': False,
            'altitude_initial': (31000.0, 'ft'),
            'altitude_final': (500.0, 'ft'),
            'altitude_bounds': ((0.0, 31500.0), 'ft'),
            'throttle_enforcement': 'path_constraint',
            'time_initial_bounds': ((112.5, 337.5), 'min'),
            'time_duration_bounds': ((26.5, 79.5), 'min'),
        },
        'initial_guesses': {'time': ([225, 53], 'min')},
    },
    'post_mission': {
        'include_landing': False,
        'constrain_range': True,
        'target_range': (1915, 'nmi'),
    },
}

Running Aviary with Updated Parameters#

Let’s run the Aviary optimization with our updated phase_info object in the same way as before.

import aviary.api as av

prob = av.run_aviary(
    'validation_cases/validation_data/test_models/aircraft_for_bench_FwFm.csv', phase_info
)
The following variables have been overridden by the aircraft definition:
  'aircraft:design:touchdown_mass_max  152800.0  lbm
  'aircraft:engine:mass  [7400.]  lbm
  'aircraft:fins:mass  0.0  lbm
  'aircraft:fuel:auxiliary_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:fuselage_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:max_capacity_mass  45694.0  lbm
  'aircraft:fuselage:passenger_compartment_length  85.5  ft
  'aircraft:fuselage:planform_area  1578.24  ft**2
  'aircraft:fuselage:wetted_area  4158.62  ft**2
  'aircraft:horizontal_tail:wetted_area  592.65  ft**2
  'aircraft:landing_gear:main_gear_oleo_length  102.0  inch
  'aircraft:landing_gear:nose_gear_oleo_length  67.0  inch
  'aircraft:vertical_tail:wetted_area  581.13  ft**2
  'aircraft:wing:aspect_ratio  11.22091  unitless
  'aircraft:wing:control_surface_area  137.0  ft**2
  'aircraft:wing:wetted_area  2396.56  ft**2
  'mission:takeoff:lift_over_drag  17.354  unitless
/home/runner/work/Aviary/Aviary/.openmdao-pixi/.pixi/envs/py313/lib/python3.13/site-packages/openmdao/core/total_jac.py:1953: DerivativesWarning:The following design variables have no impact on the constraints or objective at the current design point:
  traj.climb_1.t_initial, inds=[0]
Total number of variables............................:       66
                     variables with only lower bounds:       57
                variables with lower and upper bounds:        9
                     variables with only upper bounds:        0
Total number of equality constraints.................:       64
Total number of inequality constraints...............:       27
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:       26
        inequality constraints with only upper bounds:        0
Number of Iterations....: 22

                                   (scaled)                 (unscaled)
Objective...............:   2.5497020995242869e+00    2.5497020995242869e+00
Dual infeasibility......:   1.7762095173696346e-08    1.7762095173696346e-08
Constraint violation....:   9.7767953168684240e-15    9.7767953168684240e-15
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   9.0944744385320829e-08    9.0944744385320829e-08
Overall NLP error.......:   9.0944744385320829e-08    9.0944744385320829e-08


Number of objective function evaluations             = 28
Number of objective gradient evaluations             = 23
Number of equality constraint evaluations            = 28
Number of inequality constraint evaluations          = 28
Number of equality constraint Jacobian evaluations   = 24
Number of inequality constraint Jacobian evaluations = 24
Number of Lagrangian Hessian evaluations             = 0
Total seconds in IPOPT                               = 5.722

EXIT: Optimal Solution Found.
Aviary run successful.

Now that we’ve run Aviary, we can look at the results. Open up the automatically generated traj_results_report.html and scroll through it to visualize the results.

Here are the altitude and Mach profiles:

Altitude and Mach Profiles

We note two major changes compared to our first example.

The first is that we have many more points where the flight dynamics were evaluated because we increased num_segments to 3. This means that we have more points shown on the resulting plots.

The second is that the optimizer chose the optimal Mach profile. Again, each phase’s Mach profile is constrained to be linear because we set mach_polynomial_order and altitude_polynomial_order to 1. However, we see that the optimizer chose to decrease the Mach number during the cruise-climb segment to minimize fuel burn.

Note

Remember, we did not allow the optimizer to control the altitude profile, so that remains fixed.

Let’s take a look at the optimization objective, mission:fuel_mass:

print(prob.get_val(av.Mission.FUEL_MASS, units='kg')[0])
10204.477071171972

We can print fuel_burned in pounds easily, thanks to OpenMDAO’s automatic unit conversion feature:

print(prob.get_val(av.Mission.FUEL_MASS, units='lb')[0])
22497.02099524287

Modifying the Aircraft Configuration#

Next, we’ll modify the aircraft configuration by decreasing the wing aspect ratio by 0.2. This results in a less slender wing, which will increase the induced drag. We’ve made this change and have a modified aircraft data file called modified_aircraft.csv.

Hide code cell source

import csv

filename = 'validation_cases/validation_data/test_models/aircraft_for_bench_FwFm.csv'
filename = av.get_path(filename)

# Read the file
with open(filename, 'r') as file:
    reader = csv.reader(file)
    lines = list(reader)

# Find the index of the line containing 'aircraft:wing:span'
index = None
for i, line in enumerate(lines):
    if 'aircraft:wing:aspect_ratio' in line:
        index = i
        break

# Modify the value in the line
if index is not None:
    aspect_ratio = float(lines[index][1]) - 0.2
    lines[index][1] = str(aspect_ratio)

# Write the modified content to a new CSV file
with open(new_filename, 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerows(lines)

Re-running the Optimization with Modified Aircraft#

Now, let’s re-run the optimization with the modified aircraft configuration. We’ll use the same phase_info object as before, but we’ll change the input deck to point to our new aircraft file.

prob = av.run_aviary('modified_aircraft.csv', phase_info)
The following variables have been overridden by the aircraft definition:
  'aircraft:design:touchdown_mass_max  152800.0  lbm
  'aircraft:engine:mass  [7400.]  lbm
  'aircraft:fins:mass  0.0  lbm
  'aircraft:fuel:auxiliary_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:fuselage_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:max_capacity_mass  45694.0  lbm
  'aircraft:fuselage:passenger_compartment_length  85.5  ft
  'aircraft:fuselage:planform_area  1578.24  ft**2
  'aircraft:fuselage:wetted_area  4158.62  ft**2
  'aircraft:horizontal_tail:wetted_area  592.65  ft**2
  'aircraft:landing_gear:main_gear_oleo_length  102.0  inch
  'aircraft:landing_gear:nose_gear_oleo_length  67.0  inch
  'aircraft:vertical_tail:wetted_area  581.13  ft**2
  'aircraft:wing:aspect_ratio  11.02091  unitless
  'aircraft:wing:control_surface_area  137.0  ft**2
  'aircraft:wing:wetted_area  2396.56  ft**2
  'mission:takeoff:lift_over_drag  17.354  unitless
/home/runner/work/Aviary/Aviary/.openmdao-pixi/.pixi/envs/py313/lib/python3.13/site-packages/openmdao/core/total_jac.py:1953: DerivativesWarning:The following design variables have no impact on the constraints or objective at the current design point:
  traj.climb_1.t_initial, inds=[0]
Total number of variables............................:       66
                     variables with only lower bounds:       57
                variables with lower and upper bounds:        9
                     variables with only upper bounds:        0
Total number of equality constraints.................:       64
Total number of inequality constraints...............:       27
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:       26
        inequality constraints with only upper bounds:        0
Number of Iterations....: 22

                                   (scaled)                 (unscaled)
Objective...............:   2.5637089674782128e+00    2.5637089674782128e+00
Dual infeasibility......:   3.9054362514629958e-08    3.9054362514629958e-08
Constraint violation....:   1.2615969305981646e-14    1.2615969305981646e-14
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   9.0971568541181933e-08    9.0971568541181933e-08
Overall NLP error.......:   9.0971568541181933e-08    9.0971568541181933e-08


Number of objective function evaluations             = 28
Number of objective gradient evaluations             = 23
Number of equality constraint evaluations            = 28
Number of inequality constraint evaluations          = 28
Number of equality constraint Jacobian evaluations   = 24
Number of inequality constraint Jacobian evaluations = 24
Number of Lagrangian Hessian evaluations             = 0
Total seconds in IPOPT                               = 5.398

EXIT: Optimal Solution Found.
Aviary run successful.

The case again converged in relatively few iterations. Let’s take a look at the fuel burn value:

print(prob.get_val(av.Mission.FUEL_MASS, units='kg')[0])
10268.011155486967

As expected, it’s a bit higher than our prior run that had a larger aspect ratio.

Increasing the Polynomial Control Order#

Next, we’ll increase the mach_polynomial_order and altitude_polynomial_order to 3 for the climb and descent phases. This means that the optimizer will be able to choose a cubic Mach and altitude profile per phase instead of a straight line. We’ll use the original aircraft configuration for this run.

Note

We’ll use the IPOPT optimizer for this problem as it will handle the increased complexity better than SLSQP.

phase_info['climb_1']['user_options']['mach_polynomial_order'] = 3
phase_info['climb_1']['user_options']['altitude_polynomial_order'] = 3
phase_info['cruise']['user_options']['mach_polynomial_order'] = 1
phase_info['cruise']['user_options']['altitude_polynomial_order'] = 1
phase_info['descent_1']['user_options']['mach_polynomial_order'] = 3
phase_info['descent_1']['user_options']['altitude_polynomial_order'] = 3

prob = av.run_aviary(
    'validation_cases/validation_data/test_models/aircraft_for_bench_FwFm.csv', phase_info
)
The following variables have been overridden by the aircraft definition:
  'aircraft:design:touchdown_mass_max  152800.0  lbm
  'aircraft:engine:mass  [7400.]  lbm
  'aircraft:fins:mass  0.0  lbm
  'aircraft:fuel:auxiliary_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:fuselage_fuel_mass_capacity  0.0  lbm
  'aircraft:fuel:max_capacity_mass  45694.0  lbm
  'aircraft:fuselage:passenger_compartment_length  85.5  ft
  'aircraft:fuselage:planform_area  1578.24  ft**2
  'aircraft:fuselage:wetted_area  4158.62  ft**2
  'aircraft:horizontal_tail:wetted_area  592.65  ft**2
  'aircraft:landing_gear:main_gear_oleo_length  102.0  inch
  'aircraft:landing_gear:nose_gear_oleo_length  67.0  inch
  'aircraft:vertical_tail:wetted_area  581.13  ft**2
  'aircraft:wing:aspect_ratio  11.22091  unitless
  'aircraft:wing:control_surface_area  137.0  ft**2
  'aircraft:wing:wetted_area  2396.56  ft**2
  'mission:takeoff:lift_over_drag  17.354  unitless
/home/runner/work/Aviary/Aviary/.openmdao-pixi/.pixi/envs/py313/lib/python3.13/site-packages/openmdao/core/total_jac.py:1953: DerivativesWarning:The following design variables have no impact on the constraints or objective at the current design point:
  traj.climb_1.t_initial, inds=[0]
Total number of variables............................:       70
                     variables with only lower bounds:       57
                variables with lower and upper bounds:       13
                     variables with only upper bounds:        0
Total number of equality constraints.................:       64
Total number of inequality constraints...............:       27
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:       26
        inequality constraints with only upper bounds:        0
Number of Iterations....: 50

                                   (scaled)                 (unscaled)
Objective...............:   2.5117428911862052e+00    2.5117428911862052e+00
Dual infeasibility......:   2.9540184726225176e-03    2.9540184726225176e-03
Constraint violation....:   8.3009804018810559e-04    8.3009804018810559e-04
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   1.0002893841386246e-05    1.0002893841386246e-05
Overall NLP error.......:   2.9540184726225176e-03    2.9540184726225176e-03


Number of objective function evaluations             = 69
Number of objective gradient evaluations             = 51
Number of equality constraint evaluations            = 69
Number of inequality constraint evaluations          = 69
Number of equality constraint Jacobian evaluations   = 51
Number of inequality constraint Jacobian evaluations = 51
Number of Lagrangian Hessian evaluations             = 0
Total seconds in IPOPT                               = 13.284

EXIT: Maximum Number of Iterations Exceeded.
Warning: 
Aviary run failed. See the dashboard for more details.

And let’s print out the objective value, fuel burned:

print(prob.get_val(av.Mission.FUEL_MASS, units='kg')[0])
10032.296984905006

The added flexibility in the mission allowed the optimizer to reduce the fuel burn compared to the linear Mach profile case.

Looking at the altitude and Mach profiles, we see that the optimizer chose a more subtly complex Mach profile:

Altitude and Mach Profiles

Conclusion#

This example demonstrated how to use Aviary to optimize a more complex mission. We increased the number of segments in the mission, allowed the optimizer to choose the optimal Mach profile, and increased the polynomial control order to allow for more complex Mach profiles. We also modified the aircraft configuration to demonstrate how Aviary can be used to quickly evaluate the impact of design changes on the mission performance.