Skip to content

UAV Work from 2026 Summer Interns - #1267

Draft
ehariton wants to merge 305 commits into
OpenMDAO:mainfrom
ehariton:UAVmain
Draft

ehariton wants to merge 305 commits into
OpenMDAO:mainfrom
ehariton:UAVmain

Conversation

@ehariton

@ehariton ehariton commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Incorporating a small UAV aircraft example into aviary.

TODO:

  1. Change the overall architecture of the UAV model to be more Aviary esque including

  2. Breakup the propulsion system into Motor + ESC

  3. Break out the propellar to its own separate subsystem

  4. connect the two using the standard systems already existing in aviary

  5. rememove the balance comp from propulsion (it's probably redundant with whats already there in core avliary)

  6. add a separate subsystem for the battery or remove it all together

  7. break out the OAS model into it's own separate thing so it's not mixed in with low fidility CL/CD calcs from the UAV

  8. potentially ad reynolds number to atmospheric calculation and reorganize UAV atmosphere calcs because they are some what redundant with aviary core and potentiall output values that are unused elsewhere.

  9. remove unused average CL/CD calculations, they are not used in other locations

  10. have to change how the airfoil CSV is read in, this is probably for the propellar (not the wing). notes in test_cruse.py indicate: NOTE: no @use_tempdirs here. DBFMassBuilder reads its airfoil CSV via a repo-root- relative path (like the dbf_based_mass unit tests), so this must run from the repo root.

  11. need to have docs that describe mass builder, aero-builder (simple and medium)

  12. docs need to cover why ribs are specified as ribs = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]) each array element represents one of 20 ribs along the aircraft, and the number determines which material/thickness that rib will have. So a '0' element will be 0.0016 Balsa; '1' will be 0.0016 Ply; '2' will be 0.0024 Ply.

  13. check partials for aero_model.py and aero_OAS_examples.py

  14. many overall tests i.e. test_cruse.py are missing asserts.

  15. some tests create new phase infos which is probably unneeded.

  16. Some variables are declared in UAV_variable_meta_data.py but unused in the codebase
    -- Aircraft.Fuselage.BULKHEAD_DENSITY
    -- Aircraft.Fuselage.NUM_BULKHEADS
    -- Aircraft.HorizontalTail.NUM_RIBS
    -- Aircraft.HorizontalTail.RIB_DENSITY
    -- Aircraft.VerticalTail.NUM_RIBS
    -- Aircraft.VerticalTail.RIB_DENSITY
    -- Aircraft.Wing.NUM_RIBS
    -- Aircraft.Wing.RIB_DENSITY
    -- Dynamic.Vehicle.Propulsion.CURRENT_MAX
    -- Dynamic.Vehicle.Propulsion.RPM_MAX
    -- Dynamic.Vehicle.Propulsion.PROP_POWER_MAX

  17. Do we need an RPM_MAX assessment to keep the prop map working well

Related Issues

  • Resolves #

Backwards incompatibilities

None

AI Usage

Disclose any AI usage in this PR, including models used and files affected.

tad2216 and others added 30 commits July 6, 2026 11:03
Fixed Cruise Attempt AGAIN RPM SLACK
Changing mass builder so it is actually loaded into cruise attempt
Mass subsystem options update + renaming files
Comment on lines +304 to +327
class Averages(om.ExplicitComponent):
# averages because Aviary objectives must be scalar. not doing this would be preferable
def initialize(self):
self.options.declare('num_nodes', types=int)

def setup(self):
nn = self.options['num_nodes']

self.add_input('CD', shape=nn, units='unitless')
self.add_input('lifting_surface_CL', shape=nn, units='unitless')
self.add_output('avg_CD', units='unitless')
self.add_output('avg_CL', units='unitless')

def setup_partials(self):
nn = self.options['num_nodes']
self.declare_partials('avg_CD', 'CD', val=np.ones(nn) / nn)
self.declare_partials('avg_CL', 'lifting_surface_CL', val=np.ones(nn) / nn)

def compute(self, inputs, outputs):
total_CD = inputs['CD']
outputs['avg_CD'] = np.mean(total_CD)

total_CL = inputs['lifting_surface_CL']
outputs['avg_CL'] = np.mean(total_CL)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component might be unused - if it is used we need to understand why it was added


add_aviary_output(self, Dynamic.Atmosphere.KINEMATIC_VISCOSITY, shape=nn, units='m**2/s')

add_aviary_output(self, Dynamic.Atmosphere.DYNAMIC_PRESSURE, shape=nn, units='N/m**2')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flight_conditions.py has a component in atmosphere that already provides dynamic viscosity


add_aviary_input(self, Dynamic.Atmosphere.DYNAMIC_VISCOSITY, shape=nn, units='Pa*s')

add_aviary_output(self, Dynamic.Atmosphere.KINEMATIC_VISCOSITY, shape=nn, units='m**2/s')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have other places in Aviary where this value is calculated as-needed, perhaps flight_conditions should output it (function of dynamic viscosity and density)


add_aviary_output(self, Dynamic.Atmosphere.DYNAMIC_PRESSURE, shape=nn, units='N/m**2')

self.add_output('re', shape=nn, units='1/m')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be the only required output

Comment on lines +130 to +163
class LiftBalanceComp(om.ExplicitComponent):
def initialize(self):
self.options.declare('num_nodes', types=int)
add_aviary_option(self, Mission.GRAVITY, units='m/s**2')

def setup(self):
nn = self.options['num_nodes']
add_aviary_input(self, Dynamic.Vehicle.LIFT, shape=nn, units='N')
add_aviary_input(self, Dynamic.Vehicle.MASS, shape=nn, units='kg')

# This output will be constrained to zero.
self.add_output(
'lift_balance_residual',
val=np.zeros(nn),
units='N',
desc='Lift equilibrium residual',
)

def setup_partials(self):
nn = self.options['num_nodes']
g = self.options[Mission.GRAVITY][0] # m/s**2
rows_cols = np.arange(nn)
self.declare_partials(
'lift_balance_residual', Dynamic.Vehicle.LIFT, rows=rows_cols, cols=rows_cols, val=1.0
)
self.declare_partials(
'lift_balance_residual', Dynamic.Vehicle.MASS, rows=rows_cols, cols=rows_cols, val=-g
)

def compute(self, inputs, outputs):
L = inputs[Dynamic.Vehicle.LIFT]
m = inputs[Dynamic.Vehicle.MASS]
g = self.options[Mission.GRAVITY][0] # m/s**2
outputs['lift_balance_residual'] = L - (m * g)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be an ODE thing, not inside the aero comp. Might be duplicating a balance which will be a huge problem for EOMs that don't want lift = weight

Comment on lines +210 to +238
AeroConditions(num_nodes=nn),
promotes_inputs=[
Dynamic.Mission.VELOCITY,
Dynamic.Atmosphere.DENSITY,
Dynamic.Atmosphere.DYNAMIC_VISCOSITY,
],
promotes_outputs=[
're',
Dynamic.Atmosphere.KINEMATIC_VISCOSITY,
Dynamic.Atmosphere.DYNAMIC_PRESSURE,
],
)
atmosphere_model = aviary_inputs.get_val(Settings.ATMOSPHERE_MODEL)

self.add_subsystem(
'av_atmosphere',
AtmosphereComp(
num_nodes=nn,
h_def='geometric',
**{Settings.ATMOSPHERE_MODEL: atmosphere_model},
),
promotes_inputs=[Dynamic.Mission.ALTITUDE],
promotes_outputs=[
Dynamic.Atmosphere.DENSITY,
Dynamic.Atmosphere.DYNAMIC_VISCOSITY,
'temperature',
'speed_of_sound',
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comps are out of order (and the first component is probably redundant with our existing flight condition comp)

units='unitless',
types=str,
desc='Material density of the bulkhead',
default_value=[

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general per-rib thickness might be overkill for this fidelity level. Regardless, we should have our default be all of the same value, so default_value is "Balsa".

If we don't have per-rib thickness, then we don't want multivalue=True. If we have per-rib thickness, then we can have the default be just 'Balsa' and multivalue=True, so Aviary is expecting an array like ['Balsa', 'Balsa', ... ]

Comment on lines +8 to +11
SIMPLE:
Wing is made of solid foam and two hollow spars
MEDIUM:
Wing design includes spars, sheeting, stringers, ribs, and is hollow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genericize to "SOLID" and "HOLLOW"?

That way we aren't assuming DBF-style construction with foam and/or wood, so it is more obvious it can be used for other drones (metal, fiberglass, etc.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There aren't mass components but rather input files for a specific aircraft (basically they belong with the aircraft model alongside a csv & phase info rather than here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants