Key SGM Capabilities#

Shooting (or Forward in Time Integration) methods offer several benefits over collocation methods that some users might find useful:

  • Trajectories are physical for every iteration of the optimization (including failed optimizations)

  • Little to no initial guessing required for the trajectory

  • Dynamically ordered events and phases

Setting up ODEs#

SGM expects all of the states (including time) as inputs to the ODE and the state rates as outputs. If a particular state does not directly influence the EOM (such as distance in the GASP based climb_eom), it can be added using the built in helper functions:

from aviary.mission.gasp_based.ode.time_integration_base_classes import add_SGM_required_inputs, add_SGM_required_outputs

class ClimbODE(BaseODE):
    # ## ... ## #
    def setup(self):
        # ## ... ## #
        analysis_scheme = self.options["analysis_scheme"]
        # ## ... ## #
        if analysis_scheme is AnalysisScheme.SHOOTING:
            add_SGM_required_inputs(self, {
                't_curr': {'units': 's'},
                Dynamic.Mission.DISTANCE: {'units': 'ft'},
                'alt_trigger': {'units': self.options['alt_trigger_units'], 'val': 10e3},
                'speed_trigger': {'units': self.options['speed_trigger_units'], 'val': 100},
            })

these functions allow the user to leave the EOMs unmodified for collocation vs shooting, and provide an easy way to set the units, default values, and any other keyword args for the OpenMDAO functions add_input and add_output for any variables that only used by SGM.

Setting up Phases#

Each SGM phase should inherit from SimuPyProblem and requires an instantiated ODE. If no states are provided Aviary will attempt to determine the states in the current phase by finding the state rates (any output that ends in '_rate'). States and their rates are expected to have the same name (other than the addition of the '_rate' suffix for the state rate), if the state rate associated with a state doesn’t follow this pattern, it can be specified through alternate_state_rate_names, a dictionary with state names as the keys and the desired state rate as the value.

class SGMRotation(SimuPyProblem):
    '''
    This creates a subproblem for the rotation phase of the trajectory that will
    be solved using SGM.
    Rotation ends when the normal force on the runway reaches 0.
    '''

    def __init__(
        self,
        phase_name='rotation',
        ode_args={},
        simupy_args={},
    ):
        super().__init__(
            RotationODE(analysis_scheme=AnalysisScheme.SHOOTING, **ode_args),
            problem_name=phase_name,
            outputs=["normal_force", "alpha"],
            states=[
                Dynamic.Mission.MASS,
                Dynamic.Mission.DISTANCE,
                Dynamic.Mission.ALTITUDE,
                Dynamic.Mission.VELOCITY,
            ],
            # state_units=['lbm','nmi','ft'],
            alternate_state_rate_names={
                Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL},
            **simupy_args,
        )

        self.phase_name = phase_name
        self.add_trigger("normal_force", 0, units='lbf')

One of the main benefits of SGM is the ability to add arbitrarily ordered phases using triggers. Aviary uses an event_trigger class to store the information necessary for SGM phases. Instantiated event_triggers can be passed directly to the problem, or the helper function self.add_trigger can be used to generate the triggers. Triggers are generally used to check when the value of a state reaches a certain value, but can be used with any output from the ODE, such as normal_force in SGMRotation. Multiple triggers can be added to one phase, but the event will be triggered by whichever condition is met first.

Setting up Trajectories#

Aviary problems using the shooting method use FlexibleTraj to define their trajectories, instead of dm.Trajectory(). Similar to collocation problems, SGM will loop through the phases specified in the phase_info to build up the trajectory. When creating an SGM trajectory, the variables that will be used as inputs and outputs for states, triggers, and variables, including phase specific ones, are specified.

from aviary.mission.gasp_based.phases.time_integration_traj import FlexibleTraj
from aviary.interface.default_phase_info.two_dof_fiti import phase_info, add_default_sgm_args

add_default_sgm_args(phase_info, ode_args)

full_traj = FlexibleTraj(
    Phases=phase_info,
    traj_final_state_output=[
        Dynamic.Mission.MASS,
        Dynamic.Mission.DISTANCE,
    ],
    traj_initial_state_input=[
        Dynamic.Mission.MASS,
        Dynamic.Mission.DISTANCE,
        Dynamic.Mission.ALTITUDE,
    ],
    traj_event_trigger_input=[
        # specify ODE, output_name, with units that SimuPyProblem expects
        # assume event function is of form ODE.output_name - value
        # third key is event_idx associated with input
        ('groundroll', Dynamic.Mission.VELOCITY, 0,),
        ('climb3', Dynamic.Mission.ALTITUDE, 0,),
        ('cruise', Dynamic.Mission.MASS, 0,),
    ],
    traj_intermediate_state_output=[
        ('cruise', Dynamic.Mission.DISTANCE),
        ('cruise', Dynamic.Mission.MASS),
    ]
)

Because all phases require ode_args and simupy_args which are usually the same for all phases, add_default_sgm_args has been provided to add these to the phase info automatically.

Setting up Phase Info#

By default, SGM uses the two_dof_fiti (two degree of freedom, forward in time integration) phase_info, which contains the information required to build the default trajectory used by GASP. This phase info can be imported all at once or in a few pre-defined groups: phase_info contains all the phases from ascent_phases (which is composed of takeoff_phases and climb_phases), cruise_phases, and descent_phases.

from aviary.interface.default_phase_info.two_dof_fiti import phase_info
from aviary.interface.default_phase_info.two_dof_fiti import takeoff_phases, climb_phases, descent_phases

phase_info_parameterization can be used to update the values of certain variables, like speed_trigger or cruise_alt using values from the input deck.

Descent Fuel Estimation#

In the current formulation of the trajectory, the fuel and/or distance required for the descent are required a priori for the cruise trigger. This can be achieved by adding a submodel that contains just the descent trajectory before the main trajectory is created. The value that results from the aircraft flying this descent can then be connected to trigger values in the main trajectory.

The default descent that is flown using the two_dof_fiti phase_info is an idle descent.

from aviary.mission.gasp_based.idle_descent_estimation import add_descent_estimation_as_submodel