Understanding the Variable Hierarchy#
How Variable Hierarchies Work#
Aircraft models in Aviary usually consist of hundreds of different variables representing different aspects of the physical aircraft, behavior of the model, behavior of the mission, etc. Managing and properly connecting all those variables requires a simple framework within which we can interact with several variables at once. The idea of variable hierarchies meets this need for a simple variable framework and breaks all the variables of a model up into smaller controllable categories.
The variable hierarchy itself is rather simple. The variables are broken up into separate high level categories and each category becomes its own top-level class. Within that class we create “inner classes” (classes which are an attribute of a higher level class) that encompass a more specific category. If needed, secondary inner classes within an inner class may be created, and so on until the desired detail depth is achieved. However, too many layers down and the tedium will outweigh any value gained through specificity.
These classes and inner classes are not used for any sort of complex coding functionality. Instead they simply serve to house model variables themselves. Each class and inner class can have variables assigned to it (in object-oriented speak, the variables become an attribute of the class or inner class), and each variable assigned to a class or inner class corresponds 1-to-1 with a variable needed for the model. The variables in each class are really just identifiers that point to the actual variable name in the model. The value assigned to each variable in the classes is a string that is the name of that variable in the actual model. That string is the variable name that OpenMDAO will see, despite the fact that the user will rarely call the variable by that name. Instead the user will refer to a variable name by its hierarchy variable (eg. referring to 'aircraft:wing:span'
as Aircraft.Wing.SPAN
) to make it easier to autofill the variable and also to allow more uniform and controlled access to the variable names themselves.
Below is an example of a simple variable hierarchy that includes basic information for an aircraft:
class Aircraft:
class Fuselage:
DIAMETER = 'aircraft:fuselage:avg_diameter'
LENGTH = 'aircraft:fuselage:length'
class HorizontalTail:
ROOT_CHORD = 'aircraft:horizontal_tail:root_chord'
SPAN = 'aircraft:horizontal_tail:span'
class LandingGear:
CONFIGURATION = 'aircraft:landing_gear:configuration'
MASS = 'aircraft:landing_gear:mass'
class VerticalTail:
ROOT_CHORD = 'aircraft:vertical_tail:root_chord'
SPAN = 'aircraft:vertical_tail:span'
class Wing:
ROOT_CHORD = 'aircraft:wing:root_chord'
SPAN = 'aircraft:wing:span'
This code snippet is a notional example of how an overly simple hierarchy looks. In this example the variables all have a depth of three, meaning that all the variables are referred to in three tiers: Aircraft.Fuselage.LENGTH
, Aircraft.HorizontalTail.ROOT_CHORD
, Aircraft.Wing.SPAN
, etc. The variable names in this hierarchy can actually be used to access the values of those variables in an Aviary model, assuming that the hierarchy has been properly provided to the Aviary model. Below is an example of using a variable hierarchy variable in the add_aviary_input()
and add_aviary_output()
functions to add inputs and outputs to an OpenMDAO component for use in Aviary.
import openmdao.api as om
import aviary.api as av
class AviaryExampleComponent(om.ExplicitComponent):
def setup(self):
av.add_aviary_input(self, Aircraft.VerticalTail.ROOT_CHORD, val=4, units='ft', desc='chord of the vertical tail')
av.add_aviary_output(self, Aircraft.VerticalTail.SPAN, val=0, units='ft', desc='span of the vertical tail')
In this code snippet the variables Aircraft.VerticalTail.ROOT_CHORD
and Aircraft.VerticalTail.SPAN
from the variable hierarchy are used in the add_aviary_input
and add_aviary_output
functions to represent the actual variable names, which are aircraft:vertical_tail:root_chord
and aircraft:vertical_tail:span
respectively. The point being illustrated here is that you can use the variables from these variable hierarchies just as you would use their string-valued variable names, and in fact you must use the variable hierarchy variable and not the string-valued variable name. This way, if a change is made to the string-valued variable name in the hierarchy, that change is automatically reflected throughout all your code.
The Aviary-core Variable Hierarchies#
While the idea of a variable hierarchy can be created and used on its own, Aviary-core provides two specific variable hierarchies that are necessary for Aviary-core systems and which follow both the rules of convention and the rules of programmatic necessity laid out above. The two hierarchies provided by Aviary-core are Aircraft
and Mission
. Aircraft
houses all variables that have to do with physical aspects of the aircraft, as well as all variables that do not affect the mission or change in time. This includes several variables that are options in the model instead of inputs or outputs. Mission
on the other hand houses all variables that reference the mission or change in time. This includes things such as Mission.Design.CRUISE_ALTITUDE
and Mission.Design.RANGE
as well as things such as objectives for the optimization, parameters at various phases of flight, etc. The Aircraft
and Mission
hierarchies are the only hierarchies provided by Aviary-core, and they are both necessary for the successful running of various Aviary subsystems. Whatever additional or extended variable hierarchies have been created by the user, the end hierarchy must include the variables from the Aircraft
and Mission
hierarchies in the Aviary core, and these variables must not be altered. We offer more information below on extending and merging together variable hierarchies.
For a complete list of the variables included in the Aviary-core hierarchy visit the Variable Metadata doc page. The hierarchies themselves are located here in the repository and can be accessed in this way:
import aviary.api as av
Aircraft = av.Aircraft
Mission = av.Mission
Conventions and Requirements of the Variable Hierarchies#
There are some conventions surrounding the variable hierarchy that are required, and other things that are purely convention. Below is a list of the programmatically required aspects of the variable hierarchy:
Avoiding periods in the string-valued variable names is a must. Periods that are included here will end up as periods in an OpenMDAO variable name which will throw an error.
Including a colon after the first word in the string-valued variable name is also a must. The expressions
promotes=["aircraft:*"]
andpromotes=["mission:*"]
appear frequently within Aviary, and if these colons are not included variables will not get properly connected, leading to hard-to-detect errors.
Conversely, other things are merely convention that the Aviary team follows. Below is a list of the conventions that are not programmatically required:
Inner classes and variables are all organized alphabetically within a hierarchy. This is optional, but we follow this convention within the Aviary-core variable hierarchy to preserve cleanliness.
All variables in the hierarchy have a depth of 3. This is optional, but again we follow this convention within the Aviary-core variable hierarchy to preserve cleanliness and uniformity of code.
Including colons after all words in the string-valued variable name is optional. As stated above, it is a must after the first word for use in promotion statements, but after that it is purely a choice of convention to preserve clarity.
It is prudent at this point to pause and explain what is meant by hierarchy type. This is not a technical coding in term, instead type is an explanatory term that we use to describe the classification of a hierarchy. A hierarchy’s type is its high level category. For example, a hierarchy of the Aircraft
type would be a hierarchy that houses the variables that have to do with the physical aspects of an aircraft, and a hierarchy of the Mission
type would be a hierarchy that houses the variables which reference the mission or change in time. These are the only two types of hierarchies that are provided in Aviary-core, however, other types are possible, such as a Fleet
type hierarchy describing the variables that affect an entire fleet of aircraft, an AirTrafficControl
type hierarchy describing all the variables having to do with air traffic control, etc. The concept of the Aircraft
and Mission
type hierarchies will be used throughout the rest of this article.
Building Your Own Hierarchy#
The Aviary-core provides the pre-built variable hierarchies listed above. However, for the user that would like to add external subsystems to Aviary, the variables in the Aviary-core hierarchies may not be sufficient. In this case, there are three options: 1) extend the existing variable hierarchies from Aviary-core, 2) create your own variable hierarchies and merge them with the existing hierarchies from Aviary-core, 3) do a combination of #1 and #2. Extending a creating your own variable hierarchies are addressed below.
Extend the Existing Aviary-core Hierarchies#
If you are just adding one external subsystem to an Aviary-core model, or if you are adding multiple subsystems all being developed in the same location by the same person, our suggested path is to create one extension of the Aviary-core hierarchies. The method to extend the Aviary-core variable hierarchies is to subclass those hierarchies (which preserves all the data from the original hierarchies) and add your own additional variables as necessary. To take a simple example, let’s say we wanted to add a little bit of detail to the existing Aircraft
variable hierarchy for our external subsystem. The detail we would like to add is some information about the flaps on the wing as well as a jury strut and a center of gravity location. We would extend the existing variable hierarchy using the following code:
import aviary.api as av
AviaryAircraft = av.Aircraft
class Aircraft(AviaryAircraft):
CG = 'aircraft:center_of_gravity'
class Wing(AviaryAircraft.Wing):
class Flap:
AREA = 'aircraft:wing:flap:area'
ROOT_CHORD = 'aircraft:wing:flap:root_chord'
SPAN = 'aircraft:wing:flap:span'
class Jury:
MASS = 'aircraft:jury:mass'
There are a few things to notice about this code. The first is that, unlike the Aviary-core Aircraft
variable hierarchy, this hierarchy has variables with a depth of two and a depth of four. When we extend the Aviary-core variable hierarchies we are not restricted to the depth of three that the core hierarchies have. The second thing to notice is that when we extend an inner class that already exists in the Aviary-core hierarchy (Wing
) we subclass the inner class from the Aviary-core so that we can preserve all the information from that Aviary-core inner class. However, in the case of the inner class Jury
which does not already exist in the Aviary-core hierarchy, there is nothing to subclass, and thus that inner class stands on its own.
When extending the Aviary-core variable hierarchies, you can create one extension of each type of hierarchy, or you can create multiple extensions of each type which will eventually be merged together. If you are developing only one external subsystem or all your subsystems are being developed in the same place by the same person, one extension of each Aviary-core hierarchy should suffice. However, if you have multiple developers working on different subsystems in different locations, each developer may want to create their own extension. To aid in this case, Aviary provides the capability to merge together multiple hierarchies of the same type into one hierarchy of that type. More information on this capability is shown at the bottom of this page.
Creating Your Own Variable Hierarchy#
It is possible to create your own variable hierarchy that is not an extension of the Aviary-core variable hierarchies, but there are specific things to know. The use case for this is mostly when you would like to create a third hierarchy that does not fall under either the Aircraft
or the Mission
categories. An example of this might be if you are doing fleet-level analyses and would like a Fleet
variable hierarchy.
A new variable hierarchy can be created in exactly the same manner that Aviary-core creates its own variable hierarchies, illustrated above. For the case of creating a new Fleet
variable hierarchy, it might look something like this:
class Fleet:
class RegionalSingleAisle:
NUM_AIRCRAFT = 'fleet:rejoinal_single_aisle:number_of_aircraft'
COST_PER_AIRCRAFT = 'fleet:rejoinal_single_aisle:cost_per_aircraft'
FLIGHTS_PER_DAY = 'fleet:rejoinal_single_aisle:flights_per_day'
class JumboJet:
NUM_AIRCRAFT = 'fleet:jumbo_jet:number_of_aircraft'
COST_PER_AIRCRAFT = 'fleet:jumbo_jet:cost_per_aircraft'
FLIGHTS_PER_DAY = 'fleet:jumbo_jet:flights_per_day'
Usually if you are creating your own variable hierarchy that is not an extension of the Aviary-core hierarchies it will be because you want a hierarchy that is not of the type Aircraft
or Mission
. In this case, your new hierarchy is relatively straightforward, you can create it and simply provide it to Aviary along with the core hierarchies or extensions thereof. However, if you are creating your own variable hierarchy that is of the type Aircraft
or Mission
but is not an extension of the Aviary-core hierarchies (which we do not recommend doing), there are a few things to keep in mind:
The newly created variable hierarchy does not have any of the information from the Aviary-core hierarchy. You must still provide Aviary with the input values and information from the Aviary-core hierarchy, or all the inputs will be set to their default values which are unlikely to suit your needs.
You can merge together all variable hierarchies that are of the same type (see below).
You should avoid adding variables to your created hierarchy that already exist in the Aviary-core hierarchy, as this may cause conflicts when merging hierarchies together.
In general, we recommend extending the Aviary-core variable hierarchies whenever possible, and only creating a new hierarchy from scratch when the categories of Aircraft
and Mission
do not fit your needs.
Merging Extended Hierarchies#
As briefly mentioned above, when there are multiple variable hierarchies of the same type we need the ability to merge them together into one hierarchy of that type which includes all the information from the different hierarchies. Aviary provides this capability through the merge_hierarchies()
method. The goal of this method is to clean up all the user hierarchies so that Aviary can be provided with only one hierarchy of each type, and also to resolve any discrepancies between different hierarchies of the same type.
Using the merge_hierarchies()
function is quite simple. It takes a list of all the hierarchies you would like to merge together and returns one hierarchy that has the merged information from all the input hierarchies, as in this snippet of code where we are merging together three notional Aircraft
type hierarchies:
import aviary.api as av
FullAircraft = av.merge_hierarchies([Aircraft1, Aircraft2, Aircraft3])
When merging together variable hierarchies, make sure that all the hierarchies are of the same type. The merge_hierarchies()
will throw an error if you attempt to merge multiple hierarchies subclassed from hierarchies of different types. Also ensure that the hierarchies you are merging don’t contain the same variable with a different value. That is an impossible situation to merge, and will raise an error. Also ensure that when merging hierarchies of the Aircraft
or Mission
type the variable information from the Aviary-core hierarchies is present in your final merged hierarchies. This is handled automatically if you have built your hierarchies by extending the Aviary-core hierarchies (which is the recommended behavior), but if you have instead made your hierarchies from scratch then you will have to include the Aviary-core hierarchy of the same type in the hierarchies to be merged.
Note
If even one of the hierarchies to be merged is an extension of the Aviary-core hierarchy of that type, then the Aviary core information will be included. You only need to include the Aviary-core hierarchy of that type in the list of hierarchies to merge if none of the other hierarchies are extensions.
More syntactical data on the merging functions can be found here.
Providing Aviary with Necessary Variable Hierarchies#
Need to add content here.