I'm building a model in Dymos/OpenMDAO. There are a few states which are required for calculating the dynamics, and then some which I'd just like to derive and output. For example, say I had a very simple model with dynamic states of displacement x and velocity v, and a force input u. Then the dynamics would be x_dot = v, v_dot = u/m (where m is mass), and the OpenMDAO model would be:
class MyModel(om.ExplicitComponent):
def initialize(self):
self.options.declare('num_nodes', types=int)
def setup(self):
nn = self.options['num_nodes']
self.add_input('x', shape=(nn,), desc='displacement', units='m')
self.add_input('v', shape=(nn,), desc='velocity', units='m/s')
self.add_input('u', shape=(nn,), desc='force', units='N')
self.add_input('m', shape=(nn,), desc='mass', units='kg')
# Not rates, just states
self.add_output('y', val=np.zeros(nn), desc='derived quantity', units='...')
# State rates
self.add_output('v_dot', val=np.zeros(nn), desc='rate of change of velocity', units='m/s**2')
self.declare_partials(of='*', wrt='*', method='cs')
def compute(self, inputs, outputs):
x, v, u, m = inputs.values()
outputs['v_dot'] = u / m
outputs['y'] = # Some expression
There's an additional state y which I'd just like to compute and output, so that I can use it in path constraints etc. However, I can't just do
phase.add_state('y', rate_source='<what to put here?>', targets=['y'], units='...')
when defining the Dymos phase because the rate_source (of which there is none) can't be left unspecified. I thought about setting it as a parameter instead of a state but that requires y to be an input to the OpenMDAO model.
What would be the best way to accomplish this? Maybe I need to create an additional OpenMDAO component for the derived states and link the two together? I don't have much experience of this, so haven't explored this as an option as of yet.
States are integrated based on some governing differential equation, but in the process of computing those we might be interested in computing these "auxiliary" calculations, such as y
in this case.
To consistently have outputs in a single place, Dymos has the notion of a timeseries, where, all time, state, and control variables are placed. You can add other outputs of the ODE, such as y
to the timeseries outputs.
phase.add_timeseries_output('y')
In this case, Dymos will recognize that y
is not one of the known states, controls, or a time variable and assume that it is in the ODE. If your ODE has nested groups, you would ask for y
using the ODE-relative path.
Then you can access that output in the standard OpenMDAO way:
y = prob.get_val('<path_to_phase>.timeseries.y')
Dymos will also allow you to provide ODE outputs as boundary constraints, path constraints, or objectives. If you do this, they are automatically added to the timeseries.
phase.add_boundary_constraint('y', loc='final', lower=..., upper=...)
phase.add_path_constraint('y', lower=..., upper=...)
phase.add_objective('y', loc='final', ref=...)
In the Minimum Time Climb Example, Mach is a computed output in the aero subsystem. You can see it's added as a path constraint:
phase.add_path_constraint(name='aero.mach', lower=0.1, upper=1.8)