Skip to content

Organizing Phases into Trajectories

The majority of real-world use cases of optimal control involve complex trajectories that cannot be modeled with a single phase. For instance, different phases of a trajectory may have different equations of motion, different control parameterizations, or different path constraints. Phases are also necessary if the user wishes to impose intermediate constraints upon some variable, by imposing them as boundary constraints at a phase junction.

The Trajectory class in Dymos is intended to simplify the development of multi-phase problems. It serves as a Group which contains the various phases belonging to the trajectory, and it provides linkage constraints that dictate how phases are linked together. This enables trajectories that are not only a sequence of phases in time, but may include branching behavior, allowing us to do things like track/constrain the path of a jettisoned rocket stage.

It supports a get_values method similar to that of Phases that allows the user to retrieve the value of a variable within the trajectory. When verifying an answer with explicit simulation, the simulate method of Trajectory can simulate all of its member phases in parallel, providing a significant performance improvement for some cases.

Instantiating a Trajectory

Instantiating a Trajectory is simple. Simply invoke Trajectory(). The trajectory object itself is an OpenMDAO Group which serves as a container for its constituent Phases.

  • phases An OpenMDAO Group or ParallelGroup holding the member phases
  • linkages A Dymos PhaseLinkageComp that manages all of the linkage constraints that dictate how the phases are connected.

Adding Phases

Phases are added to a Trajectory using the add_phase method.

dymos.Trajectory.add_phase

add_phase(self, name, phase, kwargs)**

Add a phase to the trajectory.

Phases will be added to the Trajectory's phases subgroup.

Arguments:

name: The name of the phase being added.

phase: The Phase object to be added.

**kwargs: Additional arguments when adding the phase to the trajectory.

Defining Phase Linkages

Having added phases to the Trajectory, they now exist as independent Groups within the OpenMDAO model. In order to enforce continuity among certain variables across phases, the user must declare which variables are to be continuous in value at each phase boundary. There are two methods in dymos which provide this functionality. The add_linkage_constraint method provides a very general way of coupling two phases together. It does so by generating a constraint of the following form:

\begin{align} c = \mathrm{sign}_a \mathrm{var}_a + \mathrm{sign}_b \mathrm{var}_b \end{align}

Method add_linkage_constraint lets the user specify the variables and phases to be compared for this constraint, as well as the location of the variable in each phase (either 'initial' or 'final') By default this method is setup to provide continuity in a variable between two phases: - the sign of variable a is +1 while the sign of variable b is -1. - the location of variable a is 'final' while the location of variable b is 'initial'. - the default value of the constrained quantity is 0.0.

In this way, the default behavior constrains the final value of some variable in phase a to be the same as the initial value of some variable in phase b. Other values for these options can provide other functionality. For instance, to simulate a mass jettison, we could require that the initial value of mass in phase b be 1000 kg less than the value of mass at the end of phase a. Providing arguments equals = 1000, units='kg would achieve this.

Similarly, specifying other values for the locations of the variables in each phase can be used to ensure that two phases start or end at the same condition - such as the case in a branching trajectory or a rendezvous.

While add_linkage_constraint gives the user a powerful capability, providing simple state and time continuity across multiple phases would be a very verbose undertaking using this method. The link_phases method is intended to simplify this process. In the finite-burn orbit raising example, there are three phases: burn1, coast, burn2. This case is somewhat unusual in that the thrust acceleration is modeled as a state variable.
The acceleration needs to be zero in the coast phase, but continuous between burn1 and burn2, assuming no mass was jettisoned during the coast and that the thrust magnitude doesn't change.

add_linkage_constraint

dymos.Trajectory.add_linkage_constraint

add_linkage_constraint(self, phase_a, phase_b, var_a, var_b, loc_a='final', loc_b='initial', sign_a=1.0, sign_b=-1.0, units=unspecified, lower=None, upper=None, equals=None, scaler=None, adder=None, ref0=None, ref=None, linear=False, connected=False)

Explicitly add a single phase linkage constraint.

Phase linkage constraints are enforced by constraining the following equation:

sign_a * var_a + sign_b * var_b

The resulting value of this equation is constrained. This can satisfy 'coupling' or 'linkage' conditions across phase boundaries: enforcing continuity, common initial conditions, or common final conditions.

With default values, this equation can be used to enforce variable continuity at phase boundaries. For instance, constraining some variable x (either a state, control, parameter, or output of the ODE) to have the same value at the final point of phase 'foo' and the initial point of phase 'bar' is accomplished by:

add_linkage_constraint('foo', 'bar', 'x', 'x')

We may sometimes want two phases to have the same value of some variable at the start of each phase:

add_linkage_constraint('foo', 'bar', 'x', 'x', loc_a='initial', loc_b='initial')

(Here the specification of loc_b is unnecessary but helps in the clarity of whats going on.)

Or perhaps a phase has cyclic behavior. We may not know the exact value of some variable x at the start and end of the phase foo, but it must be the same value at each point.

add_linkage_constraint('foo', 'foo', 'x', 'x')

If lower, upper, and equals are all None, then dymos will use equals=0 by default. If the continuity condition is limited by some bounds instead, lower and upper can be used. For instance, perhaps the velocity ('vel') is allowed to have an impulsive change within a certain magnitude between two phases:

add_linkage_constraint('foo', 'bar', 'vel', 'vel', lower=-100, upper=100, units='m/s')

Arguments:

phase_a: The first phase in the linkage constraint.

phase_b: The second phase in the linkage constraint.

var_a: The linked variable from the first phase in the linkage constraint.

var_b: The linked variable from the second phase in the linkage constraint.

loc_a: The location of the variable in the first phase of the linkage constraint (one of 'initial' or 'final').

loc_b: The location of the variable in the second phase of the linkage constraint (one of 'initial' or 'final').

sign_a: The sign applied to the variable from the first phase in the linkage constraint.

sign_b: The sign applied to the variable from the second phase in the linkage constraint.

units: Units of the linkage. If _unspecified, dymos will use the units from the variable in the first phase of the linkage. Units of the two specified variables must be compatible.

lower: The lower bound applied as a constraint on the linkage equation.

upper: The upper bound applied as a constraint on the linkage equation.

equals: Specifies a targeted value for an equality constraint on the linkage equation.

scaler: The scaler of the linkage constraint.

adder: The adder of the linkage constraint.

ref0: The zero-reference value of the linkage constraint.

ref: The unit-reference value of the linkage constraint.

linear: If True, treat this variable as a linear constraint, otherwise False. Linear constraints should only be applied if the variable on each end of the linkage is a design variable or a linear function of one.

connected: If True, this constraint is enforced by direct connection rather than a constraint for the optimizer. This is only valid for states and time.

dymos.Trajectory.link_phases

link_phases(self, phases, vars=None, locs=('final', 'initial'), connected=False)

Specify that phases in the given sequence are to be assume continuity of the given variables.

This method caches the phase linkages, and may be called multiple times to express more complex behavior (branching phases, phases only continuous in some variables, etc).

The location at which the variables should be coupled in the two phases are provided with a two character string:

  • 'final' specifies the value at the end of the phase (at time t_initial + t_duration)
  • 'initial' specifies the value at the start of the phase (at time t_initial)

Arguments:

phases: The names of the phases in this trajectory to be sequentially linked.

vars: The variables in the phases to be linked, or ''. Providing '' will time and all states. Linking control values or rates requires them to be listed explicitly.

locs: A two-element tuple of the two-character location specification. For every pair in phases, the location specification refers to which location in the first phase is connected to which location in the second phase. If the user wishes to specify different locations for different phase pairings, those phase pairings must be made in separate calls to link_phases.

connected: Set to True to directly connect the phases being linked. Otherwise, create constraints for the optimizer to solve.

Trajectory-Level Parameters

Often times, there are parameters which apply to the entirety of a trajectory that potentially need to be optimized. If we implemented these as parameters within each phase individually, we would need some constraints to ensure that they held the same value within each phase. To avoid this complexity, Dymos Trajectory objects support their own Parameters.

Like their Phase-based counterparts, Trajectory parameters produce may be design variables for the problem or used as inputs to the trajectory from external sources.

When using Trajectory parameters, their values are connected to each phase as an Input Parameter within the Phase. Because ODEs in different phases may have different names for parameters (e.g. 'mass', 'm', 'm_total', etc) Dymos allows the user to specify the targeted ODE parameters on a phase-by-phase basis using the targets and target_params option. It can take on the following values.

  • If targets is None the trajectory parameter will be connected to the phase input parameter of the same name in each phase, if it exists (otherwise it is not connected to that phase).

  • Otherwise targets should be specified as a dictionary. And the behavior depends on the value associated with each phase name:

    • If the phase name is not in the given dictionary, attempt to connect to an existing parameter of the same name in that phase.

    • If the associated value is None, explicitly omit a connection to that phase.

    • If the associated value is a string, connect to an existing input parameter whose name is given by the string in that phase.

    • If the associated value is a Sequence, create an input parameter in that phase connected to the ODE targets given by the Sequence.

Explicit Simulation of Trajectories

The simulate method on Trajectory is similar to that of the simulate method of Phases. When invoked, it will perform a simulation of each Phase in the trajectory.