Skip to content

Commit

Permalink
Merge pull request #97 from crecine/flops_sgm
Browse files Browse the repository at this point in the history
Initial Implementation of Height Energy in SGM
  • Loading branch information
crecine authored Feb 20, 2024
2 parents 4881260 + d5a24eb commit 5e74b55
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 439 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ venv/
ENV/
env.bak/
venv.bak/
.github/*_environment.yml

# Spyder project settings
.spyderproject
Expand Down
1 change: 1 addition & 0 deletions aviary/interface/default_phase_info/two_dof_fiti.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def create_2dof_based_ascent_phases(
)
accel_vals = {}

# need to set trigger units
climb1_kwargs = dict(
input_speed_type=SpeedType.EAS,
input_speed_units='kn',
Expand Down
2 changes: 1 addition & 1 deletion aviary/interface/methods_for_level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ def load_inputs(self, aviary_inputs, phase_info=None, engine_builder=None):
elif self.mission_method is SOLVED:
from aviary.interface.default_phase_info.solved import phase_info

print('Loaded default phase_info for'
print('Loaded default phase_info for '
f'{self.mission_method.value.lower()} equations of motion')

# create a new dictionary that only contains the phases from phase_info
Expand Down
17 changes: 17 additions & 0 deletions aviary/mission/flops_based/ode/landing_ode.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
from aviary.mission.flops_based.ode.landing_eom import FlareEOM, StallSpeed
from aviary.mission.flops_based.ode.takeoff_ode import TakeoffODE as _TakeoffODE
from aviary.mission.gasp_based.flight_conditions import FlightConditions
from aviary.mission.gasp_based.ode.time_integration_base_classes import add_SGM_required_inputs
from aviary.utils.aviary_values import AviaryValues
from aviary.utils.functions import set_aviary_initial_values, promote_aircraft_and_mission_vars
from aviary.variable_info.variables import Aircraft, Dynamic, Mission
from aviary.variable_info.variables_in import VariablesIn
from aviary.variable_info.enums import AnalysisScheme


class ExternalSubsystemGroup(om.Group):
Expand Down Expand Up @@ -60,14 +62,29 @@ def initialize(self):
'external_subsystems', default=[],
desc='list of external subsystem builder instances to be added to the ODE')

self.options.declare(
"analysis_scheme",
default=AnalysisScheme.COLLOCATION,
types=AnalysisScheme,
desc="The analysis method that will be used to close the trajectory; for example collocation or time integration",
)

def setup(self):
options = self.options

nn = options["num_nodes"]
analysis_scheme = options['analysis_scheme']
aviary_options = options['aviary_options']
subsystem_options = options['subsystem_options']
core_subsystems = options['core_subsystems']

if analysis_scheme is AnalysisScheme.SHOOTING:
SGM_required_inputs = {
't_curr': {'units': 's'},
Dynamic.Mission.DISTANCE: {'units': 'm'},
}
add_SGM_required_inputs(self, SGM_required_inputs)

self.add_subsystem(
'input_port', VariablesIn(aviary_options=aviary_options),
promotes_inputs=['*'], promotes_outputs=['*'])
Expand Down
31 changes: 29 additions & 2 deletions aviary/mission/flops_based/ode/mission_ODE.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from dymos.models.atmosphere import USatm1976Comp

from aviary.mission.flops_based.ode.mission_EOM import MissionEOM
from aviary.mission.gasp_based.ode.time_integration_base_classes import add_SGM_required_inputs, add_SGM_required_outputs
from aviary.utils.aviary_values import AviaryValues
from aviary.utils.functions import promote_aircraft_and_mission_vars
from aviary.variable_info.variable_meta_data import _MetaData
from aviary.variable_info.variables import Dynamic, Mission
from aviary.variable_info.variables import Dynamic, Mission, Aircraft
from aviary.variable_info.variables_in import VariablesIn
from aviary.variable_info.enums import AnalysisScheme


class ExternalSubsystemGroup(om.Group):
Expand Down Expand Up @@ -44,15 +46,29 @@ def initialize(self):
values=['path_constraint', 'boundary_constraint', 'bounded', None],
desc='flag to enforce throttle constraints on the path or at the segment boundaries or using solver bounds'
)
self.options.declare(
"analysis_scheme",
default=AnalysisScheme.COLLOCATION,
types=AnalysisScheme,
desc="The analysis method that will be used to close the trajectory; for example collocation or time integration",
)

def setup(self):
options = self.options
nn = options['num_nodes']
analysis_scheme = options['analysis_scheme']
aviary_options = options['aviary_options']
core_subsystems = options['core_subsystems']
subsystem_options = options['subsystem_options']
engine_count = len(aviary_options.get_val('engine_models'))

if analysis_scheme is AnalysisScheme.SHOOTING:
SGM_required_inputs = {
't_curr': {'units': 's'},
Dynamic.Mission.DISTANCE: {'units': 'm'},
}
add_SGM_required_inputs(self, SGM_required_inputs)

self.add_subsystem(
'input_port',
VariablesIn(aviary_options=aviary_options,
Expand Down Expand Up @@ -169,9 +185,12 @@ def setup(self):
promotes_inputs=['*'],
promotes_outputs=['*'])

self.set_input_defaults(Dynamic.Mission.MACH, val=np.ones(nn), units='unitless')
self.set_input_defaults(Dynamic.Mission.MASS, val=np.ones(nn), units='kg')
self.set_input_defaults(Dynamic.Mission.VELOCITY, val=np.ones(nn), units='m/s')
self.set_input_defaults(Dynamic.Mission.ALTITUDE, val=np.ones(nn), units='m')
self.set_input_defaults(Dynamic.Mission.ALTITUDE_RATE,
val=np.ones(nn), units='m/s')
self.set_input_defaults(
Dynamic.Mission.THROTTLE, val=np.ones((nn, engine_count)),
units='unitless')
Expand All @@ -198,11 +217,19 @@ def setup(self):
],
promotes_outputs=['initial_mass_residual'])

if analysis_scheme is AnalysisScheme.SHOOTING:
SGM_required_outputs = {
Dynamic.Mission.ALTITUDE_RATE: {'units': 'm/s'},
}
add_SGM_required_outputs(self, SGM_required_outputs)

print_level = 0 if analysis_scheme is AnalysisScheme.SHOOTING else 2

self.nonlinear_solver = om.NewtonSolver(solve_subsystems=True,
atol=1.0e-10,
rtol=1.0e-10,
)
self.nonlinear_solver.linesearch = om.BoundsEnforceLS()
self.linear_solver = om.DirectSolver(assemble_jac=True)
self.nonlinear_solver.options['err_on_non_converge'] = True
self.nonlinear_solver.options['iprint'] = 2
self.nonlinear_solver.options['iprint'] = print_level
17 changes: 17 additions & 0 deletions aviary/mission/flops_based/ode/takeoff_ode.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

from aviary.mission.flops_based.ode.takeoff_eom import StallSpeed, TakeoffEOM
from aviary.mission.gasp_based.flight_conditions import FlightConditions
from aviary.mission.gasp_based.ode.time_integration_base_classes import add_SGM_required_inputs
from aviary.utils.aviary_values import AviaryValues
from aviary.utils.functions import set_aviary_initial_values, promote_aircraft_and_mission_vars
from aviary.variable_info.variables import Aircraft, Dynamic, Mission
from aviary.variable_info.variables_in import VariablesIn
from aviary.variable_info.enums import AnalysisScheme


class ExternalSubsystemGroup(om.Group):
Expand Down Expand Up @@ -58,14 +60,29 @@ def initialize(self):
'climbing', default=False, types=bool,
desc='mode of operation (ground roll or flight)')

self.options.declare(
"analysis_scheme",
default=AnalysisScheme.COLLOCATION,
types=AnalysisScheme,
desc="The analysis method that will be used to close the trajectory; for example collocation or time integration",
)

def setup(self):
options = self.options

nn = options["num_nodes"]
analysis_scheme = options['analysis_scheme']
aviary_options = options['aviary_options']
subsystem_options = options['subsystem_options']
core_subsystems = options['core_subsystems']

if analysis_scheme is AnalysisScheme.SHOOTING:
SGM_required_inputs = {
't_curr': {'units': 's'},
Dynamic.Mission.DISTANCE: {'units': 'm'},
}
add_SGM_required_inputs(self, SGM_required_inputs)

self.add_subsystem(
'input_port', VariablesIn(aviary_options=aviary_options),
promotes_inputs=['*'], promotes_outputs=['*'])
Expand Down
204 changes: 204 additions & 0 deletions aviary/mission/flops_based/phases/test/test_time_integration_phases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import openmdao.api as om
from openmdao.utils.assert_utils import assert_near_equal

from aviary.interface.methods_for_level2 import AviaryGroup
from aviary.mission.gasp_based.phases.time_integration_traj import FlexibleTraj
from aviary.mission.flops_based.phases.time_integration_phases import \
SGMHeightEnergy, SGMDetailedTakeoff, SGMDetailedLanding
from aviary.subsystems.premission import CorePreMission
from aviary.utils.functions import set_aviary_initial_values
from aviary.variable_info.enums import Verbosity
from aviary.variable_info.variables import Aircraft, Dynamic, Mission
from aviary.variable_info.variables_in import VariablesIn

from aviary.interface.default_phase_info.height_energy import aero, prop, geom
from aviary.subsystems.propulsion.engine_deck import EngineDeck
from aviary.utils.process_input_decks import create_vehicle
from aviary.utils.preprocessors import preprocess_propulsion
from aviary.variable_info.variable_meta_data import _MetaData as BaseMetaData

import warnings
import unittest
import importlib


@unittest.skipUnless(importlib.util.find_spec("pyoptsparse") is not None, "pyoptsparse is not installed")
class HE_SGMDescentTestCase(unittest.TestCase):
def setUp(self):
aviary_inputs, initial_guesses = create_vehicle(
'models/test_aircraft/aircraft_for_bench_FwFm.csv')
aviary_inputs.set_val(Aircraft.Engine.SCALED_SLS_THRUST, val=28690, units="lbf")
aviary_inputs.set_val(Dynamic.Mission.THROTTLE, val=0, units="unitless")
aviary_inputs.set_val(Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT,
val=0.0175, units="unitless")
aviary_inputs.set_val(Mission.Takeoff.BRAKING_FRICTION_COEFFICIENT,
val=0.35, units="unitless")
ode_args = dict(aviary_options=aviary_inputs, core_subsystems=[prop, geom, aero])
preprocess_propulsion(aviary_inputs, [EngineDeck(options=aviary_inputs)])

ode_args['num_nodes'] = 1
ode_args['subsystem_options'] = {'core_aerodynamics': {'method': 'computed'}}

self.ode_args = ode_args
self.aviary_inputs = aviary_inputs
self.tol = 1e-5

def setup_prob(self, phases) -> om.Problem:
prob = om.Problem()
prob.driver = om.pyOptSparseDriver()
prob.driver.options["optimizer"] = 'IPOPT'
prob.driver.opt_settings['tol'] = 1.0E-6
prob.driver.opt_settings['mu_init'] = 1e-5
prob.driver.opt_settings['max_iter'] = 50
prob.driver.opt_settings['print_level'] = 5

aviary_options = self.ode_args['aviary_options']
subsystems = self.ode_args['core_subsystems']

traj = FlexibleTraj(
Phases=phases,
promote_all_auto_ivc=True,
traj_final_state_output=[Dynamic.Mission.MASS,
Dynamic.Mission.DISTANCE,
Dynamic.Mission.ALTITUDE],
traj_initial_state_input=[
Dynamic.Mission.MASS,
Dynamic.Mission.DISTANCE,
Dynamic.Mission.ALTITUDE,
],
)
prob.model = AviaryGroup(aviary_options=aviary_options,
aviary_metadata=BaseMetaData)
prob.model.add_subsystem(
'pre_mission',
CorePreMission(aviary_options=aviary_options,
subsystems=subsystems),
promotes_inputs=['aircraft:*', 'mission:*'],
promotes_outputs=['aircraft:*', 'mission:*']
)
prob.model.add_subsystem('traj', traj,
promotes=['aircraft:*', 'mission:*']
)

prob.model.add_subsystem(
"fuel_obj",
om.ExecComp(
"reg_objective = overall_fuel/10000",
reg_objective={"val": 0.0, "units": "unitless"},
overall_fuel={"units": "lbm"},
),
promotes_inputs=[
("overall_fuel", Mission.Summary.TOTAL_FUEL_MASS),
],
promotes_outputs=[("reg_objective", Mission.Objectives.FUEL)],
)

prob.model.add_objective(Mission.Objectives.FUEL, ref=1e4)

prob.model.add_subsystem(
'input_sink',
VariablesIn(aviary_options=self.aviary_inputs,
meta_data=BaseMetaData),
promotes_inputs=['*'],
promotes_outputs=['*'])

with warnings.catch_warnings():

# Set initial default values for all LEAPS aircraft variables.
set_aviary_initial_values(
prob.model, self.aviary_inputs, meta_data=BaseMetaData)

warnings.simplefilter("ignore", om.PromotionWarning)

prob.setup()

return prob

def run_simulation(self, phases, initial_values: dict):
prob = self.setup_prob(phases)

for key, val in initial_values.items():
prob.set_val(key, **val)

prob.run_model()

distance = prob.get_val('traj.distance_final', units='NM')[0]
mass = prob.get_val('traj.mass_final', units='lbm')[0]
alt = prob.get_val('traj.altitude_final', units='ft')[0]

final_states = {'distance': distance, 'mass': mass, 'altitude': alt}
return final_states

# def test_takeoff(self):
# initial_values_takeoff = {
# "traj.altitude_initial": {'val': 0, 'units': "ft"},
# "traj.mass_initial": {'val': 171000, 'units': "lbm"},
# "traj.distance_initial": {'val': 0, 'units': "NM"},
# "traj.velocity": {'val': .1, 'units': "m/s"},
# }

# ode_args = self.ode_args
# ode_args['friction_key'] = Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT
# brake_release_to_decision = SGMDetailedTakeoff(
# ode_args,
# simupy_args=dict(verbosity=Verbosity.DEBUG,)
# )
# brake_release_to_decision.clear_triggers()
# brake_release_to_decision.add_trigger(Dynamic.Mission.VELOCITY, value=167.85, units='kn')

# phases = {'HE': {
# 'ode': brake_release_to_decision,
# 'vals_to_set': {}
# }}

# final_states = self.run_simulation(phases, initial_values_takeoff)
# # assert_near_equal(final_states['altitude'], 500, self.tol)
# assert_near_equal(final_states['velocity'], 167.85, self.tol)

def test_cruise(self):
initial_values_cruise = {
"traj.altitude_initial": {'val': 35000, 'units': "ft"},
"traj.mass_initial": {'val': 171000, 'units': "lbm"},
"traj.distance_initial": {'val': 0, 'units': "NM"},
"traj.mach": {'val': .8, 'units': "unitless"},
}

SGMCruise = SGMHeightEnergy(
self.ode_args,
phase_name='cruise',
simupy_args=dict(verbosity=Verbosity.QUIET,)
)
SGMCruise.triggers[0].value = 160000

phases = {'HE': {
'ode': SGMCruise,
'vals_to_set': {}
}}

final_states = self.run_simulation(phases, initial_values_cruise)
assert_near_equal(final_states['mass'], 160000, self.tol)

# def test_landing(self):
# initial_values_landing = {
# "traj.altitude_initial": {'val': 35000, 'units': "ft"},
# "traj.mass_initial": {'val': 171000, 'units': "lbm"},
# "traj.distance_initial": {'val': 0, 'units': "NM"},
# "traj.velocity": {'val': 300, 'units': "m/s"},
# }

# ode_args = self.ode_args
# ode_args['friction_key'] = Mission.Takeoff.BRAKING_FRICTION_COEFFICIENT
# phases = {'HE': {
# 'ode': SGMDetailedLanding(
# ode_args,
# simupy_args=dict(verbosity=Verbosity.QUIET,)
# ),
# 'vals_to_set': {}
# }}

# final_states = self.run_simulation(phases, initial_values_landing)
# assert_near_equal(final_states['altitude'], 0, self.tol)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 5e74b55

Please sign in to comment.