Mission Optimization with Many Phases for a Commercial Aircraft

Mission Optimization with Many Phases for a Commercial Aircraft#

So far within these example docs we have been building up the complexity of our coupled aircraft-mission design problem. In the simple mission example, we flew the aircraft in straight line phases. In the more advanced mission example, we allowed the optimizer to find the optimal Mach profiles for phases.

In this example, we will build on the prior examples by adding more phases to the mission. This will allow us to model more complex missions, such as a commercial aircraft flying a long-haul route with multiple cruise segments, intermediary climb segments, and a cruise-climb segment.

Problem Formulation#

We use the aviary draw_mission GUI as shown below:

multiple_phases_gui

This results in the following phase_info dictionary:

phase_info = {
    "pre_mission": {"include_takeoff": False, "optimize_mass": True},
    "climb_1": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.2, "unitless"),
            "final_mach": (0.72, "unitless"),
            "mach_bounds": ((0.18, 0.74), "unitless"),
            "initial_altitude": (0.0, "ft"),
            "final_altitude": (31000.0, "ft"),
            "altitude_bounds": ((0.0, 31500.0), "ft"),
            "throttle_enforcement": "path_constraint",
            "fix_initial": True,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((0.0, 0.0), "min"),
            "duration_bounds": ((25.5, 76.5), "min"),
        },
        "initial_guesses": {"time": ([0, 51], "min")},
    },
    "cruise_1": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.72, "unitless"),
            "final_mach": (0.72, "unitless"),
            "mach_bounds": ((0.7, 0.74), "unitless"),
            "initial_altitude": (31000.0, "ft"),
            "final_altitude": (31000.0, "ft"),
            "altitude_bounds": ((30500.0, 31500.0), "ft"),
            "throttle_enforcement": "boundary_constraint",
            "fix_initial": False,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((25.5, 76.5), "min"),
            "duration_bounds": ((23.5, 70.5), "min"),
        },
        "initial_guesses": {"time": ([51, 47], "min")},
    },
    "climb_2": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.72, "unitless"),
            "final_mach": (0.74, "unitless"),
            "mach_bounds": ((0.7, 0.76), "unitless"),
            "initial_altitude": (31000.0, "ft"),
            "final_altitude": (33000.0, "ft"),
            "altitude_bounds": ((30500.0, 33500.0), "ft"),
            "throttle_enforcement": "boundary_constraint",
            "fix_initial": False,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((49.0, 147.0), "min"),
            "duration_bounds": ((5.0, 15.0), "min"),
        },
        "initial_guesses": {"time": ([98, 10], "min")},
    },
    "cruise_2": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.74, "unitless"),
            "final_mach": (0.74, "unitless"),
            "mach_bounds": ((0.72, 0.76), "unitless"),
            "initial_altitude": (33000.0, "ft"),
            "final_altitude": (33000.0, "ft"),
            "altitude_bounds": ((32500.0, 33500.0), "ft"),
            "throttle_enforcement": "boundary_constraint",
            "fix_initial": False,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((54.0, 162.0), "min"),
            "duration_bounds": ((24.0, 72.0), "min"),
        },
        "initial_guesses": {"time": ([108, 48], "min")},
    },
    "climb_3": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.74, "unitless"),
            "final_mach": (0.76, "unitless"),
            "mach_bounds": ((0.72, 0.78), "unitless"),
            "initial_altitude": (33000.0, "ft"),
            "final_altitude": (34500.0, "ft"),
            "altitude_bounds": ((32500.0, 35000.0), "ft"),
            "throttle_enforcement": "boundary_constraint",
            "fix_initial": False,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((78.0, 234.0), "min"),
            "duration_bounds": ((7.0, 21.0), "min"),
        },
        "initial_guesses": {"time": ([156, 14], "min")},
    },
    "climb_4": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.76, "unitless"),
            "final_mach": (0.76, "unitless"),
            "mach_bounds": ((0.74, 0.78), "unitless"),
            "initial_altitude": (34500.0, "ft"),
            "final_altitude": (36000.0, "ft"),
            "altitude_bounds": ((34000.0, 36500.0), "ft"),
            "throttle_enforcement": "boundary_constraint",
            "fix_initial": False,
            "constrain_final": False,
            "fix_duration": False,
            "initial_bounds": ((85.0, 255.0), "min"),
            "duration_bounds": ((43.0, 129.0), "min"),
        },
        "initial_guesses": {"time": ([170, 86], "min")},
    },
    "descent_1": {
        "subsystem_options": {"core_aerodynamics": {"method": "computed"}},
        "user_options": {
            "optimize_mach": False,
            "optimize_altitude": False,
            "polynomial_control_order": 1,
            "num_segments": 3,
            "order": 3,
            "solve_for_distance": False,
            "initial_mach": (0.76, "unitless"),
            "final_mach": (0.2, "unitless"),
            "mach_bounds": ((0.18, 0.78), "unitless"),
            "initial_altitude": (36000.0, "ft"),
            "final_altitude": (500.0, "ft"),
            "altitude_bounds": ((0.0, 36500.0), "ft"),
            "throttle_enforcement": "path_constraint",
            "fix_initial": False,
            "constrain_final": True,
            "fix_duration": False,
            "initial_bounds": ((128.0, 384.0), "min"),
            "duration_bounds": ((41.0, 123.0), "min"),
        },
        "initial_guesses": {"time": ([256, 82], "min")},
    },
    "post_mission": {
        "include_landing": False,
        "constrain_range": True,
        "target_range": (2393, "nmi"),
    },
}

Running Aviary with Updated Parameters#

Let’s now run Aviary with this multiphase mission and view the results.

import aviary.api as av

prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv',
                     phase_info, optimizer="SLSQP", make_plots=True)
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:902: OMDeprecationWarning:None: The method `add_polynomial_control` is deprecated and will be removed in Dymos 2.1. Please use `add_control` with the appropriate options to define a polynomial control.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'climb_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'climb_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'cruise_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'cruise_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'climb_2': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'climb_2': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'cruise_2': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'cruise_2': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'climb_3': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'climb_3': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'climb_4': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'climb_4': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'mach' in phase 'descent_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/dymos/phase/phase.py:2328: RuntimeWarning: Invalid options for non-optimal control 'altitude' in phase 'descent_1': lower, upper, ref
  warnings.warn(f"Invalid options for non-optimal control '{name}' in phase "
The following variables have been overridden:
  'aircraft:design:touchdown_mass
  'aircraft:engine:mass
  'aircraft:fins:mass
  'aircraft:fuel:auxiliary_fuel_capacity
  'aircraft:fuel:fuselage_fuel_capacity
  'aircraft:fuel:total_capacity
  'aircraft:fuselage:planform_area
  'aircraft:fuselage:wetted_area
  'aircraft:horizontal_tail:wetted_area
  'aircraft:landing_gear:main_gear_oleo_length
  'aircraft:landing_gear:nose_gear_oleo_length
  'aircraft:vertical_tail:wetted_area
  'aircraft:wing:aspect_ratio
  'aircraft:wing:control_surface_area
  'aircraft:wing:wetted_area
--- Constraint Report [traj] ---
    --- climb_1 ---
        [path]    0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- cruise_1 ---
        [initial] 0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
        [final]   0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- climb_2 ---
        [initial] 0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
        [final]   0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- cruise_2 ---
        [initial] 0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
        [final]   0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- climb_3 ---
        [initial] 0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
        [final]   0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- climb_4 ---
        [initial] 0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
        [final]   0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
    --- descent_1 ---
        [path]    0.0000e+00 <= throttle <= 1.0000e+00  [unitless]
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.cruise_1.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.climb_2.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.cruise_2.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.climb_3.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.climb_4.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/solvers/linear/linear_rhs_checker.py:178: SolverWarning:DirectSolver in 'traj.phases.descent_1.indep_states' <class StateIndependentsComp>: 'rhs_checking' is active but no redundant adjoint dependencies were found, so caching has been disabled.
Model viewer data has already been recorded for Driver.
Full total jacobian for problem 'aircraft_for_bench_FwFm' was computed 3 times, taking 1.664703194000026 seconds.
Total jacobian shape: (164, 135) 


Jacobian shape: (164, 135)  (4.89% nonzero)
FWD solves: 10   REV solves: 0
Total colors vs. total size: 10 vs 135  (92.59% improvement)

Sparsity computed using tolerance: 1e-25
Time to compute sparsity:   1.6647 sec
Time to compute coloring:   0.0575 sec
Memory to compute coloring:   0.0000 MB
Coloring created on: 2024-09-16 15:49:34
Optimization terminated successfully    (Exit mode 0)
            Current function value: 3.105029798355184
            Iterations: 16
            Function evaluations: 16
            Gradient evaluations: 16
Optimization Complete
-----------------------------------
/usr/share/miniconda/envs/test/lib/python3.12/site-packages/openmdao/core/driver.py:143: OMDeprecationWarning:boolean evaluation of DriverResult is temporarily implemented to mimick the previous `failed` return behavior of run_driver.
Use the `success` attribute of the returned DriverResult object to test for successful driver completion.

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

Note

Remember, we did not allow the optimizer to control either the Mach or the altitude profiles. The optimizer varied the phase durations until the optimal mission profile was found.

What Next?#

The point of this doc page is to show that missions can be arbitrarily complex in terms of the number of phases and how they’re classified. If you want multiple climb, cruise, descent phases, that’s absolutely something Aviary can handle.

There are a lot of options for how you could modify this example. You could:

  • enable the optimize_mach or optimize_altitude flags

  • increase the polynomial_control_order so there’s more flexibility in the optimized mission

  • try different target_range values for the full mission range

  • add an external subsystem to the phases

Playing around with a model and seeing how different settings affect the optimization and resulting aircraft design is always an enlightening experience.