Skip to content
45 changes: 3 additions & 42 deletions aviary/mission/energy_state/phases/build_landing.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
import openmdao.api as om

from aviary.mission.energy_state.phases.simplified_landing import LandingGroup
from aviary.variable_info.variables import Aircraft, Mission


class Landing:
"""
Define user constraints for a climb phase.

Parameters
----------
ref_wing_area : float (0.0)
reference are of wing (ft^2)
Cl_max_ldg : float (None)
maximum coefficient of lift in landing configuration (None)

Returns
-------
Group
a Group object in OpenMDAO
"""

def __init__(
self,
ref_wing_area=None,
Cl_max_ldg=None,
):
self.ref_wing_area = ref_wing_area # ft**2
self.Cl_max_ldg = Cl_max_ldg # no units
# note: need to clean this up so that some of these variables come from
# connections.

__slots__ = (
'ref_wing_area',
'Cl_max_ldg',
)

def build_phase(self, use_detailed=False):
"""
Construct and return a new phase for landing analysis.
Expand All @@ -56,15 +24,8 @@ def build_phase(self, use_detailed=False):
'Must set landing method to `use_detailed=False`, detailed landing is'
' not currently enabled.'
)

##############
# Add Inputs #
##############

landing = LandingGroup()
landing.set_input_defaults(Aircraft.Wing.AREA, val=self.ref_wing_area, units='ft**2')
landing.set_input_defaults(
Mission.Landing.LIFT_COEFFICIENT_MAX, val=self.Cl_max_ldg, units='unitless'
)
else:
# Simple landing group
landing = LandingGroup()

return landing
79 changes: 31 additions & 48 deletions aviary/mission/energy_state/phases/simplified_landing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import openmdao.api as om

from aviary.constants import GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_METRIC
from aviary.constants import GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_ENGLISH
from aviary.subsystems.atmosphere.atmosphere import Atmosphere
from aviary.variable_info.functions import add_aviary_input, add_aviary_output
from aviary.variable_info.variables import Aircraft, Dynamic, Mission
Expand All @@ -10,36 +10,19 @@ class LandingCalc(om.ExplicitComponent):
"""Calculate the distance covered over the ground and approach velocity during landing."""

def setup(self):
add_aviary_input(self, Mission.FINAL_MASS, val=150_000)

add_aviary_input(
self,
Dynamic.Atmosphere.DENSITY,
val=1.225,
units='kg/m**3',
desc='atmospheric density',
)

add_aviary_input(self, Aircraft.Wing.AREA, val=700)

add_aviary_input(self, Mission.Landing.LIFT_COEFFICIENT_MAX, val=3)
add_aviary_input(self, Mission.FINAL_MASS, units='lbm')
add_aviary_input(self, Dynamic.Atmosphere.DENSITY, units='slug/ft**3')
add_aviary_input(self, Aircraft.Wing.AREA, units='ft**2')
add_aviary_input(self, Mission.Landing.LIFT_COEFFICIENT_MAX, units='unitless')

add_aviary_output(self, Mission.Landing.GROUND_DISTANCE, val=0)
add_aviary_output(self, Mission.Landing.GROUND_DISTANCE, units='ft')
add_aviary_output(self, Mission.Landing.INITIAL_VELOCITY, units='ft/s')

add_aviary_output(self, Mission.Landing.INITIAL_VELOCITY, val=0)

self.declare_partials(
Mission.Landing.INITIAL_VELOCITY,
[
Mission.Landing.LIFT_COEFFICIENT_MAX,
Aircraft.Wing.AREA,
Mission.FINAL_MASS,
],
)
self.declare_partials(Mission.Landing.INITIAL_VELOCITY, '*')
self.declare_partials(Mission.Landing.GROUND_DISTANCE, '*')

def compute(self, inputs, outputs):
rho_SL = RHO_SEA_LEVEL_METRIC
rho_SL = RHO_SEA_LEVEL_ENGLISH
landing_weight = inputs[Mission.FINAL_MASS] * GRAV_ENGLISH_LBM
rho = inputs[Dynamic.Atmosphere.DENSITY]
planform_area = inputs[Aircraft.Wing.AREA]
Expand All @@ -51,14 +34,16 @@ def compute(self, inputs, outputs):
# distance covered during landing, which should be less.

Cl_app = Cl_ldg_max / 1.3**2
V_app = 17.18644 * (landing_weight / (planform_area * Cl_app)) ** 0.5

V_app = ((2 * landing_weight) / (rho * planform_area * Cl_app)) ** 0.5

landing_distance = 2500 + 105 * landing_weight / (planform_area * rho_ratio * Cl_app * 1.69)

outputs[Mission.Landing.GROUND_DISTANCE] = landing_distance
outputs[Mission.Landing.INITIAL_VELOCITY] = V_app

def compute_partials(self, inputs, J):
rho_SL = RHO_SEA_LEVEL_METRIC
rho_SL = RHO_SEA_LEVEL_ENGLISH
landing_weight = inputs[Mission.FINAL_MASS] * GRAV_ENGLISH_LBM
rho = inputs[Dynamic.Atmosphere.DENSITY]
planform_area = inputs[Aircraft.Wing.AREA]
Expand All @@ -68,29 +53,27 @@ def compute_partials(self, inputs, J):

Cl_app = Cl_ldg_max / 1.3**2

J[Mission.Landing.INITIAL_VELOCITY, Mission.Landing.LIFT_COEFFICIENT_MAX] = (
17.18644
* 0.5
* (landing_weight / (planform_area * Cl_app)) ** (-0.5)
* (-landing_weight)
/ (planform_area * Cl_app**2)
/ 1.3**2
# INITIAL_VELOCITY (V_app) Partials
V_app = ((2 * landing_weight) / (rho * planform_area * Cl_app)) ** 0.5
d_sqrt = 0.5 / V_app

J[Mission.Landing.INITIAL_VELOCITY, Mission.FINAL_MASS] = (
d_sqrt * (2 * GRAV_ENGLISH_LBM) / (rho * planform_area * Cl_app)
)

J[Mission.Landing.INITIAL_VELOCITY, Dynamic.Atmosphere.DENSITY] = (
d_sqrt * (-2 * landing_weight) / (rho**2 * planform_area * Cl_app)
)

J[Mission.Landing.INITIAL_VELOCITY, Aircraft.Wing.AREA] = (
17.18644
* 0.5
* (landing_weight / (planform_area * Cl_app)) ** (-0.5)
* (-landing_weight)
/ (planform_area**2 * Cl_app)
d_sqrt * (-2 * landing_weight) / (rho * planform_area**2 * Cl_app)
)
J[Mission.Landing.INITIAL_VELOCITY, Mission.FINAL_MASS] = (
17.18644
* 0.5
* (landing_weight / (planform_area * Cl_app)) ** (-0.5)
* GRAV_ENGLISH_LBM
/ (planform_area * Cl_app)

J[Mission.Landing.INITIAL_VELOCITY, Mission.Landing.LIFT_COEFFICIENT_MAX] = (
d_sqrt * (-2 * landing_weight) / (rho * planform_area * Cl_app**2) / 1.3**2
)

# GROUND DISTANCE Partials:
J[Mission.Landing.GROUND_DISTANCE, Mission.FINAL_MASS] = (
105 * GRAV_ENGLISH_LBM / (planform_area * rho_ratio * Cl_app * 1.69)
)
Expand All @@ -107,8 +90,8 @@ def compute_partials(self, inputs, J):

class LandingGroup(om.Group):
"""
Calculate the distance covered over the ground and approach velocity
during landing with atmosphere is included.
Calculate the estimated Landing field length and velocity given the aircraft properties and atmospheric conditions.
Note this is not the actual distance covered over the ground.
"""

def setup(self):
Expand Down
17 changes: 10 additions & 7 deletions aviary/mission/energy_state/phases/test/test_building_landing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,25 @@
from openmdao.utils.testing_utils import use_tempdirs

from aviary.mission.energy_state.phases.build_landing import Landing
from aviary.variable_info.variables import Mission
from aviary.variable_info.variables import Aircraft, Mission


@use_tempdirs
class LandingPhaseTest(unittest.TestCase):
"""Test landing phase builder."""

def test_case1(self):
landing_options = Landing(
ref_wing_area=1370.0, # ft**2
Cl_max_ldg=3, # no units
)
landing_options = Landing()

use_detailed = False
landing = landing_options.build_phase(use_detailed=use_detailed)

prob = om.Problem()
prob.model = landing
prob.model.set_input_defaults(Mission.FINAL_MASS, val=150000.0, units='lbm')
prob.model.set_input_defaults(Mission.Landing.INITIAL_ALTITUDE, val=0, units='ft')
prob.model.set_input_defaults(Aircraft.Wing.AREA, val=1370.0, units='ft**2')
prob.model.set_input_defaults(Mission.Landing.LIFT_COEFFICIENT_MAX, val=3, units='unitless')
prob.setup(force_alloc_complex=True)
prob.run_model()
partial_data = prob.check_partials(
Expand All @@ -31,8 +32,10 @@ def test_case1(self):
assert_check_partials(partial_data, atol=1e-12, rtol=1e-12)

tol = 1e-6
assert_near_equal(prob[Mission.Landing.GROUND_DISTANCE], 6332.4878059, tol)
assert_near_equal(prob[Mission.Landing.INITIAL_VELOCITY], 134.9752, tol)
assert_near_equal(prob[Mission.Landing.GROUND_DISTANCE], 6332.13214907, tol)
assert_near_equal(
prob.get_val(Mission.Landing.INITIAL_VELOCITY, units='kn'), 134.97550621, tol
)


if __name__ == '__main__':
Expand Down
50 changes: 21 additions & 29 deletions aviary/mission/energy_state/phases/test/test_simplified_landing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,14 @@ def setUp(self):
promotes=['*'],
)

self.prob.model.set_input_defaults(Mission.FINAL_MASS, val=152800.0, units='lbm')
self.prob.model.set_input_defaults(
Mission.FINAL_MASS, val=152800.0, units='lbm'
) # check (this is the design landing mass)
self.prob.model.set_input_defaults(
Dynamic.Atmosphere.DENSITY,
val=constants.RHO_SEA_LEVEL_METRIC,
units='kg/m**3',
) # not exact value but should be close enough
self.prob.model.set_input_defaults(
Aircraft.Wing.AREA, val=1370.0, units='ft**2'
) # check (this is the reference wing area)
Dynamic.Atmosphere.DENSITY, val=constants.RHO_SEA_LEVEL_METRIC, units='kg/m**3'
)
self.prob.model.set_input_defaults(Aircraft.Wing.AREA, val=1370.0, units='ft**2')
self.prob.model.set_input_defaults(
Mission.Landing.LIFT_COEFFICIENT_MAX, val=3, units='unitless'
) # check
)

self.prob.setup(check=False, force_alloc_complex=True)

Expand All @@ -43,11 +37,10 @@ def test_case1(self):

tol = 1e-5

assert_near_equal(self.prob[Mission.Landing.GROUND_DISTANCE], 6403.64963504, tol)
assert_near_equal(
self.prob[Mission.Landing.GROUND_DISTANCE], 6403.64963504, tol
) # not actual value
# not actual value
assert_near_equal(self.prob[Mission.Landing.INITIAL_VELOCITY], 136.22914933, tol)
self.prob.get_val(Mission.Landing.INITIAL_VELOCITY, units='kn'), 136.22914933, tol
)

partial_data = self.prob.check_partials(out_stream=None, method='cs')
assert_check_partials(partial_data, atol=1e-12, rtol=1e-12)
Expand All @@ -73,6 +66,12 @@ def test_case1(self):
LandingCalc(),
promotes=['*'],
)
prob.model.set_input_defaults(Mission.FINAL_MASS, val=152800.0, units='lbm')
prob.model.set_input_defaults(
Dynamic.Atmosphere.DENSITY, val=constants.RHO_SEA_LEVEL_METRIC, units='kg/m**3'
)
prob.model.set_input_defaults(Aircraft.Wing.AREA, val=1370.0, units='ft**2')
prob.model.set_input_defaults(Mission.Landing.LIFT_COEFFICIENT_MAX, val=3, units='unitless')
prob.setup(check=False, force_alloc_complex=True)

partial_data = prob.check_partials(out_stream=None, method='cs')
Expand All @@ -91,18 +90,12 @@ def setUp(self):
promotes=['*'],
)

self.prob.model.set_input_defaults(
Mission.FINAL_MASS, val=152800.0, units='lbm'
) # check (this is the design landing mass)
self.prob.model.set_input_defaults(
Mission.Landing.INITIAL_ALTITUDE, val=35, units='ft'
) # confirm initial altitude should be 35 ft.
self.prob.model.set_input_defaults(
Aircraft.Wing.AREA, val=1370.0, units='ft**2'
) # check (this is the reference wing area)
self.prob.model.set_input_defaults(Mission.FINAL_MASS, val=152800.0, units='lbm')
self.prob.model.set_input_defaults(Mission.Landing.INITIAL_ALTITUDE, val=35, units='ft')
self.prob.model.set_input_defaults(Aircraft.Wing.AREA, val=1370.0, units='ft**2')
self.prob.model.set_input_defaults(
Mission.Landing.LIFT_COEFFICIENT_MAX, val=3, units='unitless'
) # check
)

self.prob.setup(check=False, force_alloc_complex=True)

Expand All @@ -111,11 +104,10 @@ def test_case1(self):

tol = 1e-5

assert_near_equal(self.prob[Mission.Landing.GROUND_DISTANCE], 6407.65299289, tol)
assert_near_equal(
self.prob[Mission.Landing.GROUND_DISTANCE], 6407.65299289, tol
) # not actual value
# not actual value
assert_near_equal(self.prob[Mission.Landing.INITIAL_VELOCITY], 136.22914933, tol)
self.prob.get_val(Mission.Landing.INITIAL_VELOCITY, units='kn'), 136.29923391, tol
)

partial_data = self.prob.check_partials(
out_stream=None, excludes=['*.standard_atmosphere'], method='cs'
Expand Down
15 changes: 3 additions & 12 deletions aviary/mission/energy_state_problem_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,7 @@ def add_post_mission_systems(self, aviary_group):
)

if aviary_group.post_mission_info['include_landing']:
if 'aircraft:wing:area' in aviary_group.aviary_inputs:
self._add_landing_systems(aviary_group)
else:
print('Aircraft.Wing.AREA is not given. Set include_landing = False')
aviary_group.post_mission_info['include_landing'] = False
self._add_landing_systems(aviary_group)

aviary_group.add_subsystem(
'range_constraint',
Expand Down Expand Up @@ -490,14 +486,9 @@ def _add_post_mission_takeoff_systems(self, aviary_group):
)

def _add_landing_systems(self, aviary_group):
landing_options = Landing(
ref_wing_area=aviary_group.aviary_inputs.get_val(Aircraft.Wing.AREA, units='ft**2'),
Cl_max_ldg=aviary_group.aviary_inputs.get_val(
Mission.Landing.LIFT_COEFFICIENT_MAX
), # no units
)
landing_options = Landing()

landing = landing_options.build_phase(False)
landing = landing_options.build_phase(use_detailed=False)

aviary_group.add_subsystem(
'landing',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ aircraft:wing:thickness_to_chord_distribution,-1.0,0.15,0.1132,0.0928,0.0822,0.0
aircraft:wing:ultimate_load_factor,3.75,unitless
aircraft:wing:var_sweep_mass_penalty,0,unitless
mission:constraints:max_mach,0.85,unitless
mission:landing:initial_velocity,140,ft/s
mission:landing:lift_coefficient_max,3.0,unitless
mission:takeoff:braking_friction_coefficient,0.3,unitless
mission:takeoff:rolling_friction_coefficient,0.025,unitless
Expand All @@ -104,6 +103,7 @@ AERIN.FLTO,11000
AERIN.ITPAER,2
AERIN.MYAERO,0
AERIN.THROFF,0.25
AERIN.VAPPR,140
AERIN.XLLAM,0
CONFIN.CH,39000
CONFIN.GW,874099
Expand Down
2 changes: 1 addition & 1 deletion aviary/utils/test/data/converter_test_BWB_simple_FLOPS.csv
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ aircraft:wing:thickness_to_chord,0.11,unitless
aircraft:wing:ultimate_load_factor,3.75,unitless
aircraft:wing:var_sweep_mass_penalty,0,unitless
mission:constraints:max_mach,0.85,unitless
mission:landing:initial_velocity,140,ft/s
mission:landing:lift_coefficient_max,3.0,unitless
mission:takeoff:braking_friction_coefficient,0.3,unitless
mission:takeoff:rolling_friction_coefficient,0.025,unitless
Expand All @@ -103,6 +102,7 @@ AERIN.FLTO,11000
AERIN.ITPAER,2
AERIN.MYAERO,0
AERIN.THROFF,0.25
AERIN.VAPPR,140
AERIN.XLLAM,0
CONFIN.CH,39000
CONFIN.GW,874099
Expand Down
Loading
Loading