Python API#
Aviary has a Python interface to allow users to configure and run their analysis.
The basic Python interface is defined in the aviary/interface/run_aviary.py file and it has a method run_aviary() with a few arguments. If you examine interface/run_aviary.py you see that it prepares those arguments and then calls run_aviary_problem(). For aviary run_mission validation_cases/validation_data/test_models/aircraft_for_bench_GwGm.csv examples, those arguments and their values are:
aircraft_data:aircraft_for_bench_GwGm.csvphase_info: not provided (and will be loaded fromaviary/models/missions/two_dof_default.py)optimizer:Noneobjective_type:Nonerestart_filename:Nonemax_iter:50run_driver:Truemake_plots:Truephase_info_modifier:Noneverbosity:None
All the above arguments are straightforward except objective_type. Even though objective_type is None, it is not treated as None. In this scenario, the objective is set based on problem_type when using the 2DOF mission method. There are three options for problem_type which is set to SIZING as default when aircraft is created. Aviary has the following mapping when user does not set objective_type but set mission_method to 2DOF (in .csv file):
|
|
|---|---|
|
|
|
|
|
|
|
No Default, user specified |
In Aviary, problem_type is set to SIZING when it creates a vehicle (see create_vehicle). As you can see, since problem_type is SIZING by default in our case and we don’t manually alter this setting, Aviary set objective to Mission.Objectives.FUEL. We will discuss more options of objective_type later on.
Note
If you want to use a custom objective function, you can set any arbitrary variable to be the objective by directly calling the OpenMDAO add_objective method instead of using Aviary’s built-in add_objective() method.
In our onboarding runs, we want to limit the number of iterations to 1 so that they all run faster. As a result, we will not consider whether the optimization converges. So, we will have
max_iter: 1
You can follow the following steps in order (we do not include function arguments for simplicity):
prob = AviaryProblem()load_inputs()modify the aviary values dictionary if needed
check_and_preprocess_inputs()build_model()add_pre_mission_systems()add_phases()add_post_mission_systems()link_phases()Add custom promotions, connections if needed
add_driver()add_design_variables()add_objective()setup()Set custom starting values using set_val if needed
run_aviary_problem()
In the rest of this page, we will show a few examples to demonstrate these steps. We start from rebuilding aircraft_for_bench_GwGm model in great details.
Build AviaryProblem for the same Model#
We create a level 2 Python script to reproduce the aircraft_for_bench_GwGm model run that was used as an example in the level 1 document (this time we won’t use the level 1 functionality). The methods listed above are defined in level 3 (namely, core/aviary_problem.py). You can run the code as follows:
Note
For these examples we have set max_iter = 0, which means that the optimization will not run. This is done to reduce the computational time for the examples. If you want to run the optimization, you can set max_iter = 100 or some similar value.
from copy import deepcopy
import aviary.api as av
# inputs that run_aviary() requires
aircraft_data = 'validation_cases/validation_data/test_models/aircraft_for_bench_GwGm.csv'
optimizer = 'IPOPT'
objective_type = None
restart_filename = None
max_iter = 0
phase_info = deepcopy(av.default_2DOF_phase_info)
# Build problem
prob = av.AviaryProblem()
# Load aircraft and options data from user
# Allow for user overrides here
prob.load_inputs(aircraft_data, phase_info)
prob.check_and_preprocess_inputs()
# check the aircraft_data and phase_info for errors
# adds a pre-mission group (propulsion, geometry, aerodynamics, and mass)
# adds a sequence of core mission phases.
# adds a landing phase
# Link phases and variables
prob.build_model()
# adds an optimizer to the driver
prob.add_driver(optimizer, max_iter=max_iter)
# adds relevant design variables
prob.add_design_variables()
# Load optimization problem formulation
# Detail which variables the optimizer can control
prob.add_objective(objective_type=objective_type)
# setup the problem and set initial guesses of states and controls variables
prob.setup()
# run the problem we just set up
prob.run_aviary_problem(restart_filename=restart_filename)
The following variables have been overridden by the aircraft definition:
'aircraft:anti_icing:mass 551.0 lbm
'aircraft:apu:mass 928.0 lbm
'aircraft:avionics:mass 1959.0 lbm
'aircraft:design:emergency_equipment_mass 50.0 lbm
'aircraft:design:wing_loading 128.0 lbf/ft**2
'aircraft:furnishings:mass 11192.0 lbm
'aircraft:fuselage:wetted_area 4000.0 ft**2
'aircraft:horizontal_tail:form_factor 1.25 unitless
'aircraft:horizontal_tail:moment_ratio 0.2307 unitless
'aircraft:horizontal_tail:volume_coefficient 1.189 unitless
'aircraft:nacelle:form_factor [1.5] unitless
'aircraft:strut:fuselage_interference_factor 0.0 unitless
'aircraft:vertical_tail:form_factor 1.25 unitless
'aircraft:vertical_tail:moment_ratio 2.362 unitless
'aircraft:vertical_tail:volume_coefficient 0.145 unitless
'aircraft:wing:form_factor 1.25 unitless
'aircraft:wing:slat_span_ratio 0.9 unitless
req_fuel_mass > max_wingfuel_mass, adding a body tank
Total number of variables............................: 225
variables with only lower bounds: 75
variables with lower and upper bounds: 149
variables with only upper bounds: 0
Total number of equality constraints.................: 221
Total number of inequality constraints...............: 34
inequality constraints with only lower bounds: 2
inequality constraints with lower and upper bounds: 32
inequality constraints with only upper bounds: 0
Number of Iterations....: 0
(scaled) (unscaled)
Objective...............: 5.1722886909006149e+00 5.1722886909006149e+00
Dual infeasibility......: 1.6500000000000000e+01 1.6500000000000000e+01
Constraint violation....: 8.8081681383747128e+00 8.8081681383747128e+00
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 2.9553974865183619e+01 2.9553974865183619e+01
Overall NLP error.......: 2.9553974865183619e+01 2.9553974865183619e+01
Number of objective function evaluations = 1
Number of objective gradient evaluations = 1
Number of equality constraint evaluations = 1
Number of inequality constraint evaluations = 1
Number of equality constraint Jacobian evaluations = 1
Number of inequality constraint Jacobian evaluations = 1
Number of Lagrangian Hessian evaluations = 0
Total seconds in IPOPT = 1.506
EXIT: Maximum Number of Iterations Exceeded.
Warning:
Aviary run failed. See the dashboard for more details.
In this code, you do the same import as run_aviary.py does and set the values of all the arguments in run_aviary(). Now we will go through each line in detail to explain each step:
Dissection of level 2 for the same aircraft_for_bench_GwGm model#
All the methods of prob object (including its creation) are defined in level 2 (aviary_problem.py). We now look at each of them.
We add other inputs that run_aviary() requires:
aircraft_data = 'validation_cases/validation_data/test_models/aircraft_for_bench_GwGm.csv'
optimizer = 'IPOPT'
objective_type = None
restart_filename = None
max_iter = 1
prob = av.AviaryProblem()
Several objects are initialized in this step:
self.model = AviaryGroup()
# This causes problems where aviary_inputs are stored in different places for
# multi-mission vs. standard mission
self.aviary_inputs = None
self.aviary_groups_dict = {}
self.meta_data = meta_data
# TODO try and find a better solution than a new custom flag - the issue is multimission
# problems don't have a consistent variable path to check the inputs later on
self.generate_payload_range = False
phase_info is a user defined dictionary (in a Python file) that controls the profile of the mission to be simulated (e.g. climb, cruise, descent segments etc). The line
phase_info = deepcopy(av.default_2DOF_phase_info)
prob.load_inputs(aircraft_data, phase_info)
prob.check_and_preprocess_inputs()
is a function that has a few tasks:
Read aircraft deck file
aircraft_dataRead phase info file
phase_infoBuild core subsystems
Check and preprocess all inputs
We have seen aircraft_data file (a .csv file) in our level 1 examples. In level 1, we simply called it input file. An aircraft model can also be directly defined in Python, by setting up an AviaryValues object with the desired inputs and options normally found in an input file. That object can be provided in the place of aircraft_data.
Engines are built by using the input data aircraft:engine:data_file in the .csv file. For example in aircraft_for_bench_GwGm.csv file, we see:
aircraft:engine:data_file,models/engines/turbofan_23k_1.csv,unitless
So, aircraft:engine:data_file has value models/engines/turbofan_23k_1.csv,unitless. We follow this path and open that file. The top rows of engine deck file are:
| Mach Number (input) | Altitude (ft | input) | Throttle (input) | Gross Thrust (lbf | output) | Ram Drag (lbf | output).1 | Fuel Flow (lb/h | output).2 | NOx Rate (lb/h | output).3 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.0 | 0.0 | 21.0 | 1446.4 | 0.0 | 842.2 | 4.7876 | NaN | NaN | NaN | NaN | NaN |
| 1 | 0.0 | 0.0 | 22.0 | 2314.3 | 0.0 | 976.0 | 5.1926 | NaN | NaN | NaN | NaN | NaN |
| 2 | 0.0 | 0.0 | 24.0 | 4049.9 | 0.0 | 1281.0 | 6.6532 | NaN | NaN | NaN | NaN | NaN |
| 3 | 0.0 | 0.0 | 26.0 | 5785.6 | 0.0 | 1659.9 | 8.2234 | NaN | NaN | NaN | NaN | NaN |
| 4 | 0.0 | 0.0 | 30.0 | 9642.7 | 0.0 | 2621.6 | 12.0950 | NaN | NaN | NaN | NaN | NaN |
Users can provide an EngineModel instance of their own to use in Aviary’s propulsion systems.
Other subsystems, including mass, geometry, and aerodynamics, are set up according to which legacy code options the user has specified in their input file, using settings:equations_of_motion and settings:mass_method. Aerodynamics is set up to match the selected equations of motion, while geometry will use either GASP, FLOPS, or both methods as required to calculate all values needed by other subsystems.
Next we check the user-provided inputs. The prob.check_and_preprocess_inputs method checks the user-supplied input values for any potential problems. These problems include variable names that are not recognized in Aviary, conflicting options or values, or units mismatching. You only want to check and pre-process your inputs once so after loading your inputs, make sure to update anything you need to before you check the values.
Next, we use build_model to build the model which includes: adding pre-mission systems including propulsion, geometry, aerodynamics, and mass subsystems. Then a sequence of core mission phases is added. In addition, if mission_method is 2DOF and groundroll and rotation phases are in the model, then it adds an equality constraint to ensure that the velocity at the end of the groundroll phase is equal to the rotation velocity at the start of the rotation phase. If mission_method is energy_state, it sets up trajectory parameters by calling setup_trajectory_params(). If mission_method is solved_2DOF, it has a block of code to make sure that the trajectory is smooth by applying boundary constraints between phases (e.g. fuselage pitch angle or true airspeed).
For energy_state missions, aviary currently models FLOPS’ “simplified” takeoff as defined in mission/energy_state/phases/simplified_takeoff.py.
It follows by adding post-mission subsystems which includes landing phase if include_landing key of post_mission has value of True. If the user chooses to define a post_mission, it will override the default. For 2DOF missions, landing is defined in mission/two_dof/ode/landing_ode.py.
Following this, the phases are linked together. This is important for allowing each phase of flight to pass to the next without discontinuities in the parameters. Consider Dymos’ Aircraft Balanced Field Length Calculation example. In that example, we see separate nonlinear boundary constraints, nonlinear path constraints, and phase continuity constraints between phases. We don’t want to go deeper in this function call, but just point out that each individual link can be set via dymos function link_phases(). See dymos API for more details. The links are set up based on physical principals (e.g. you can’t have instantaneous changes in mass, velocity, position etc.). Special care is required if the user selects a different or unusual set of phases.
Note
prob.build_model can be broken out into four separate commands if finer control to each step is desired. These commands should be executed in order and are:
prob.add_pre_mission_systems
prob.add_phases
prob.add_post_mission_systems
prob.link_phases
prob.build_model()
Now, our aircraft and the mission are fully defined. We are ready to define an optimization problem. This is achieved by adding an optimization driver, adding design variables, and an objective.
For add_driver() function, we accept its argument use_coloring=None. Coloring is a technique that OpenMDAO uses to compute partial derivatives efficiently. This will become important later.
prob.add_driver(optimizer, max_iter=max_iter)
Available drivers for use in Aviary are SLSQP, SNOPT, and IPOPT. The table below summarizes the basic setting along with sample values (the settings are options required by each optimizer):
Optimizers |
Drivers |
Settings |
|---|---|---|
|
om.pyOptSparseDriver() |
|
|
om.pyOptSparseDriver() |
|
|
om.ScipyOptimizeDriver() |
|
Note that SLSQP is freely available, but its performance is not as good as SNOPT and IPOPT sometimes. SNOPT is a commercial optimizer, and it is free for academic use. IPOPT is an open-source optimizer and it is free for all users.
Design variables (and constraints) are set in the line prob.add_design_variables():
prob.add_design_variables()
The details of the design variables and constraints are in the source code of add_design_variables(). Let us summarize the data below:
For default energy_state mission model, it is relatively simple:
Design Variables |
Lower Bound |
Upper Bound |
Reference Value |
Units |
|---|---|---|---|---|
Aircraft.Design.GROSS_MASS |
10 |
900.e3 |
175.e3 |
lbm |
For default 2DOF mission model, the design variables and constraints depend on the type of problems (SIZING, OFF_DESIGN_MIN_FUEL, or OFF_DESIGN_MAX_RANGE, see ProblemType class in aviary/variable_info/enums.py for details). First, there are four common design variables and two common constraints. There are two more design variables and two constraints for sizing problems.
Problem Type |
Design Variables |
Lower Bound |
Upper Bound |
Reference Value |
Units |
|---|---|---|---|---|---|
Any |
Mission.Takeoff.ASCENT_T_INITIAL |
0 |
100 |
30.0 |
s |
Any |
Mission.Takeoff.ASCENT_DURATION |
1 |
1000 |
10.0 |
s |
Any |
tau_gear |
0.01 |
1.0 |
1 |
s |
Any |
tau_flaps |
0.01 |
1.0 |
1 |
s |
{glue:md}’SIZING’ |
Aircraft.Design.GROSS_MASS |
10. |
None |
175_000 |
lbm |
{glue:md}’SIZING’ |
Mission.GROSS_MASS |
10. |
None |
175_000 |
lbm |
{glue:md}’OFF_DESIGN_MIN_FUEL’ |
Mission.GROSS_MASS |
0 |
infinite |
175_000 |
lbm |
Problem Type |
Constraint |
Relation |
Value |
Reference Value |
Units |
Any |
h_fit.h_init_gear |
= |
50.0 |
50.0 |
ft |
Any |
h_fit.h_init_flaps |
= |
400.0 |
400.0 |
ft |
{glue:md}’SIZING’ |
Mission.Constraints.RANGE_RESIDUAL |
= |
0 |
10 |
unitless |
{glue:md}’OFF_DESIGN_MIN_FUEL’ |
Mission.Constraints.RANGE_RESIDUAL |
= |
0 |
10 |
lbm |
In the above table, there are two hard-coded design variables: tau_gear and tau_flaps. They represent fractions of ascent time to start gear retraction and flaps retraction. There are two hard-coded constraints: h_fit.h_init_gear and h_fit.h_init_flaps. They are the altitudes of initial gear retraction and initial flaps retraction. The underscore in number ‘175_000’ is for readability only.
There are other constraints using OpenMDAO’s EQConstraintComp component. We will not go into the details as this part is complicated and needs special attention. Note that each subsystem (for example engine model) might have their own design variables (think, for example, sizing the engine). Aviary goes through all subsystems and adds appropriate design variables.
You can override all the functions in level 3. So, you can set up your constraints in level 3 if the above ones do not meet your requirements.
The optimization objective is added to the problem by this line:
prob.add_objective(objective_type=objective_type)
The selection of objective is a little complicated.
Earlier in this page, we have discussed the objective when objective_type=None and mission_method is 2DOF. Let us discuss the other situations.
There are several objective types that users can choose: mass, hybrid_objective, fuel_burned, and fuel.
objective_type |
objective |
|---|---|
mass |
|
hybrid_objective |
|
fuel_burned |
|
fuel |
|
As listed in the above, if objective_type="mass", the objective is the final value of Dynamic.Vehicle.MASS at the end of the mission.
If objective_type="fuel", the objective is the Mission.Objectives.FUEL.
There is a special objective type: hybrid_objective. When objective_type='hybrid_objective', the objective is a mix of minimizing fuel burn and minimizing the mission duration:
obj = -final_mass / {takeoff_mass} + final_time / 5.
This is because if we just minimized fuel burn then the optimizer would probably fly the plane slowly to save fuel, but we actually care about some mix of minimizing fuel burn while providing a reasonable travel time for the passengers. This leads to the hybrid_objective which seeks to minimize a combination of those two objectives. final_time is the duration of the full mission and is usually in the range of hours. So, the denominator 5. means 5 hours. That’s just a value to scale the final_time variable. Since it’s a composite objective we didn’t want to have OpenMDAO do the scaling because the two variables in the objective are of a different order of magnitude.
If objective_type=None for a 2DOF mission, Aviary will choose the objective based on mission_method and problem_type. We have discussed this case earlier in this page.
Note: Aviary variable Mission.Objectives.FUEL when using the 2DOF mission is actually a hybrid objective defined as
reg_objective = overall_fuel/10000 + ascent_duration/30.
where overall_fuel has the unit of lbm and ascent_duration has the unit of seconds. In our case, settings:equations_of_motion = 2DOF, the final value of objective is [5.17228869], with ref: 1.0 and units: blank. The units should be interpreted as unitless.
Here, ref is the reference value. For different objectives, the range may vary significantly different. We want to normalize the value. Ideally, users should choose ref such that the objective is in the range of (0,1). This is required by optimizer.
Note: Unfortunately, not all objective_type and mission_method combinations work.
Next is a line to call
prob.setup()
This is a lightly wrapped OpenMDAO setup() and prob.set_initial_guesses method for the problem. It allows us to do pre- and post-setup changes, like adding calls to set_input_defaults and do some simple set_vals if needed as well as setting initial guesses.
If we look at the signature of setup() in OpenMDAO’s Problem class, we find that the available kwargs are: check, logger, mode, force_alloc_complex, distributed_vector_class, local_vector_class, and derivatives. The ones that Aviary uses are check and force_alloc_complex. Argument check is a flag to determine default checks are performed. Default checks are: ‘auto_ivc_warnings’, comp_has_no_outputs’, ‘dup_inputs’, ‘missing_recorders’, ‘out_of_order’, ‘solvers’, ‘system’, ‘unserializable_options’.
If force_alloc_complex is true, sufficient memory will be allocated to allow nonlinear vectors to store complex values while operating under complex step. For our example, we don’t use any of them.
For optimization problems, initial guesses are important. prob.setup does setup initial guesses as well.
For solved_2DOF and 2DOF missions, the prob.set_initial_guesses method performs several calls to set_val on the trajectory for states and controls to seed the problem with reasonable initial guesses using initial_guesses within corresponding phases (e.g. energy_state.py and two_dof.py). For solved_2DOF missions, it performs similar tasks but for hard-coded state parameters. This is reasonable because a solved_2DOF mission is actually a level 3 Aviary approach. We will cover it in level 3 onboarding doc in the next page. Note that initial guesses for all phases are especially important for collocation methods.
The last line is to run the problem we just set up:
prob.run_aviary_problem()
This is a simple wrapper of Dymos’ run_problem() function. It allows the users to provide, restart_filename, suppress_solver_print, and run_driver. In our case, restart_filename is set to None. The rest of the arguments take default values. If a restart file name is provided, aviary (or dymos) will load the states, controls, and parameters as given in the provided case as the initial guess for the next run. We have discussed the .db file in level 1 onboarding doc and will discuss how to use it to generate useful output in level 3 onboarding doc.
Finally, we can add a few print statements for the variables that we are interested:
print('Mission.Objectives.FUEL', prob.get_val(Mission.Objectives.FUEL, units='unitless'))
print('Mission.TOTAL_FUEL', prob.get_val(Mission.TOTAL_FUEL_MASS, units='lbm'))
print(
'Mission.GROSS_MASS (takeoff_mass)',
prob.get_val(Mission.GROSS_MASS, units='lbm'),
)
print(
'Mission.FINAL_MASS',
prob.get_val(Mission.FINAL_MASS, units='lbm'),
)
print()
print(
'Groundroll Final Mass (lbm)',
prob.get_val('traj.groundroll.states:mass', units='lbm')[-1],
)
print('Rotation Final Mass (lbm)', prob.get_val('traj.rotation.states:mass', units='lbm')[-1])
print('Ascent Final Mass (lbm)', prob.get_val('traj.ascent.states:mass', units='lbm')[-1])
print('Accel Final Mass (lbm)', prob.get_val('traj.accel.states:mass', units='lbm')[-1])
print('Climb1 Final Mass (lbm)', prob.get_val('traj.climb1.states:mass', units='lbm')[-1])
print('Climb2 Final Mass (lbm)', prob.get_val('traj.climb2.states:mass', units='lbm')[-1])
print('Cruise Final Mass (lbm)', prob.get_val('traj.cruise.states:mass', units='lbm')[-1])
print('Desc1 Final Mass (lbm)', prob.get_val('traj.desc1.states:mass', units='lbm')[-1])
print('Desc2 Final Mass (lbm)', prob.get_val('traj.desc2.states:mass', units='lbm')[-1])
print('done')
Mission.Objectives.FUEL [5.17228869]
Mission.TOTAL_FUEL [43389.55357567]
Mission.GROSS_MASS (takeoff_mass) [175400.]
Mission.FINAL_MASS [136000.]
Groundroll Final Mass (lbm) [173646.]
Rotation Final Mass (lbm) [173646.]
Ascent Final Mass (lbm) [173646.]
Accel Final Mass (lbm) [173646.]
Climb1 Final Mass (lbm) [173646.]
Climb2 Final Mass (lbm) [173646.]
Cruise Final Mass (lbm) [135000.]
Desc1 Final Mass (lbm) [136000.]
Desc2 Final Mass (lbm) [136000.]
done
We will cover user customized outputs in level 3.
Level 2: Another example#
We now use a similar aircraft, a large single aisle commercial transport aircraft, but with a different mass estimation and mission method. Let us run Aviary using this input deck in level 1 first.
!aviary run_mission validation_cases/validation_data/test_models/aircraft_for_bench_FwFm.csv --max_iter 0 --optimizer IPOPT
/home/runner/work/Aviary/Aviary/.openmdao-pixi/.pixi/envs/py313/lib/python3.13/site-packages/modopt/core/visualization.py:11: UserWarning: matplotlib not found, plotting disabled.
warnings.warn("matplotlib not found, plotting disabled.")
/home/runner/work/Aviary/Aviary/.openmdao-pixi/.pixi/envs/py313/lib/python3.13/site-packages/openmdao/core/constants.py:16: OMDeprecationWarning:The INF_BOUND sentinel in OpenMDAO is deprecated. Infinite bounds should now be specified using None or +/-np.inf.
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
Total number of variables............................: 96
variables with only lower bounds: 93
variables with lower and upper bounds: 3
variables with only upper bounds: 0
Total number of equality constraints.................: 94
Total number of inequality constraints...............: 61
inequality constraints with only lower bounds: 1
inequality constraints with lower and upper bounds: 60
inequality constraints with only upper bounds: 0
Number of Iterations....: 0
(scaled) (unscaled)
Objective...............: 3.9800875570924603e+00 3.9800875570924603e+00
Dual infeasibility......: 7.8792026331682263e-01 7.8792026331682263e-01
Constraint violation....: 1.9006004373650107e+00 1.9006004373650107e+00
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 7.9560101797999998e+00 7.9560101797999998e+00
Overall NLP error.......: 7.9560101797999998e+00 7.9560101797999998e+00
Number of objective function evaluations = 1
Number of objective gradient evaluations = 1
Number of equality constraint evaluations = 1
Number of inequality constraint evaluations = 1
Number of equality constraint Jacobian evaluations = 1
Number of inequality constraint Jacobian evaluations = 1
Number of Lagrangian Hessian evaluations = 0
Total seconds in IPOPT = 0.792
EXIT: Maximum Number of Iterations Exceeded.
Warning:
Aviary run failed. See the dashboard for more details.
Once again, to convert it to a level 2 model, we need to set all the arguments in level 1 manually.
By running a model in level 2 directly, we have the flexibility to modify the input parameters (e.g. phase_info). Let us continue to make modifications and obtain a different run script shown below:
phase_info = {
'pre_mission': {
'include_takeoff': False,
'optimize_mass': False,
},
'cruise': {
'subsystem_options': {'aerodynamics': {'method': 'computed'}},
'user_options': {
'num_segments': 2,
'order': 3,
'mach_optimize': False,
'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_polynomial_order': 1,
'altitude_initial': (35000.0, 'ft'),
'altitude_final': (35000.0, 'ft'),
'altitude_bounds': ((23000.0, 38000.0), 'ft'),
'throttle_enforcement': 'boundary_constraint',
'time_initial_bounds': ((0.0, 0.0), 'min'),
'time_duration_bounds': ((10.0, 30.0), 'min'),
},
'initial_guesses': {'time': ([0, 30], 'min')},
},
'post_mission': {
'include_landing': False,
},
}
# inputs that run_aviary() requires
aircraft_data = 'validation_cases/validation_data/test_models/aircraft_for_bench_FwFm.csv'
mission_method = 'energy_state'
mass_method = 'FLOPS'
optimizer = 'SLSQP'
objective_type = None
restart_filename = None
# Build problem
prob = av.AviaryProblem()
# Load aircraft and options data from user
# Allow for user overrides here
prob.load_inputs(aircraft_data, phase_info)
prob.check_and_preprocess_inputs()
prob.build_model()
prob.add_driver(optimizer, max_iter=0)
prob.add_design_variables()
# Load optimization problem formulation
# Detail which variables the optimizer can control
prob.add_objective(objective_type=objective_type)
prob.setup()
prob.run_aviary_problem()
print('done')
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.cruise.t_initial, inds=[0]
Iteration limit reached (Exit mode 9)
Current function value: 3.9800875570924603
Iterations: 0
Function evaluations: 1
Gradient evaluations: 1
Optimization FAILED.
Iteration limit reached
-----------------------------------
Warning:
Aviary run failed. See the dashboard for more details.
done
As you see, there is a single phase cruise, no takeoff, no landing. Note that we must set include_takeoff to False because Aviary internally tries to connect takeoff to climb phase which we don’t provide. There should be a check to see if both takeoff and climb phase exist first. Aviary still has many things to be improved.
We will see more details for what users can do in level 3.
Level 2 is where you can integrate user-defined external subsystems, which is one of the main features of the Aviary tool. Examples of external subsystems are: acoustics, battery modeling, etc.
Assume that you already have an external subsystem that you want to incorporate it into your model. We show how to add external subsystems via the optional load_external_subsystems() method of the AviaryProblem.
We will cover external subsystems in details in Models with External Subsystems page.
Summary#
As you see, level 2 is more flexible than level 1. In level 2, you can:
add/remove pre-defined mission phases (via
phase_info, see example above);scale design variables (via reference value in
phase_info)import additional files (e.g.
aero_data)set pre-defined objective (e.g.
hybrid_objective)add external subsystems
set
use_coloring(see example above).
Most Aviary users should be well-served by Level 2; we have purposefully constructed it to be capable of most all use cases, even those on the forefront of research in aircraft design.
That being said, there are some cases where Level 2 is not sufficient and you may need additional flexibility. We are ready to move on to Level 3.