Skip to content

Commit 7d1846a

Browse files
authored
Vectorize all the things (OpenMDAO#103)
* added ODE and a notional spacecraft reorientation test problem to test out vectorized states, controls, and parameters. * fixed indexing for RPM phases, PhaseSimulationResults remains to be fixed. uncovered an issue with computing state value continuity when using "compressed" transcription. ex_min_time_climb.py demonstrates the issue. * added a brachistochrone in which the position is a vector, uncovered a bug in simulated results * fixed recording for parameters, now design and input parameters dont have their entire time-history saved to the file. Updated the commercial aircraft example to integrate climb_rate to get altitude, and not surprisingly its much more consistent in its result now. * error in sparsity of test_aircraft_cruise when using gauss-lobatto, debugging * fixed test by making climb_rate a control and not a design parameter * removed spacecraft reorientation example for now * restored use of the Agg backend in examples * fix to install openmdao documentation * retry travis fix * fixed an error in table formatting * fixed an error in table formatting * Added unit tests for indexing, finite burn orbit EOM, and testing of the vector states brachistochrone example. * utils.misc unit tests * updated double integrator example to use other states/controls as rate sources. parameters no longer need to have targets, for degenerate cases such as this where the parameter is purely there as a rate source. * pep8 fixes * disc -> state_disc in gauss lobatto get rate source path * disc -> state_disc * sizing issue with design and input parameters in phase simulation results * double integrator example switched back to SLSQP
1 parent f842029 commit 7d1846a

36 files changed

+959
-367
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ install:
113113
# install OpenMDAO in developer mode so we have access to its sphinx extensions
114114
- git clone https://github.com/OpenMDAO/OpenMDAO.git;
115115
- cd OpenMDAO;
116-
- pip install -e .;
116+
- pip install -e .[all];
117117
- cd ..;
118118

119119
# install dymos itself in developer mode.

dymos/docs/examples/commercial_aircraft.rst

+19-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ In this case we also use the steady flight assumption to allow the problem
2121
to be parameterized with a different control set. Rather than providing
2222
throttle/thrust and angle of attack as control variables, we use a *differential
2323
inclusion* approach. Whereas altitude and velocity are often treated as
24-
state variables to be integrated, here we specify them as controls.
24+
state variables to be integrated, here we take a slightly different approach.
2525

2626
Rather than using the differential equations to compute rates for the
2727
flight path angle and true airspeed, we use a nonlinear solver to determine
@@ -41,6 +41,7 @@ presence of nonlinear solvers can lead to inaccurate derivatives based on the pr
4141
of the solver. With OpenMDAO's unified derivatives framework, the sensitivy to
4242
solver tolerances is greatly reduced.
4343

44+
4445
Pitfalls of Differential Inclusion
4546
==================================
4647

@@ -51,6 +52,17 @@ of the vehicle. In our case, the throttle parameter :math:`$\tau$` is path cons
5152
within the optimizer, so that the optimizer can make the solution physically attainable
5253
by our model.
5354

55+
We have two options for how to tackle this problem. We could specify altitude as a control
56+
variable, and use it's approximate derivative (`alt_rate`) to provide the altitude rate for
57+
the flight equilibrium conditions. In practice, however, this noisy derivative of altitude
58+
can cause numerical difficulties. Other times, the optimizer will shape the control to satisfy
59+
our equilibrium conditions at the collocation nodes but be wildly wrong in between these nodes.
60+
One simple way to get around this is to still integrate altitude, but to provide `climb_rate`
61+
as the control variable. This makes it easy to constraint `climb_rate` with bounds constraints,
62+
and the time-history of altitude will be smoother since it is the time integral of the rate of
63+
climb.
64+
65+
5466
Solver Options
5567
##############
5668

@@ -105,16 +117,17 @@ Name Description Fixed Initial Value Fixed Final Value
105117
========= ============================== =================== ===================
106118
range distance flown along ground True (0 NM) False
107119
mass_fuel mass of fuel aboard aircraft True (30000 lbm) True (0 lbm)
120+
alt aircraft altitude True (10000 ft) True (10000 ft)
108121
========= ============================== =================== ===================
109122

110123
Dynamic Controls
111124
================
112125

113-
===== ============================== ========= =================== ===================
114-
Name Description Optimized Fixed Initial Value Fixed Final Value
115-
===== ============================== ========= =================== ===================
116-
alt aircraft altitude True True (10000 ft) True (10000 ft)
117-
===== ============================== ========= =================== ===================
126+
========== ============================== ========= =================== ===================
127+
Name Description Optimized Fixed Initial Value Fixed Final Value
128+
========== ============================== ========= =================== ===================
129+
climb_rate aircraft rate of climb True False False
130+
========== ============================== ========= =================== ===================
118131

119132
Design Parameters
120133
=================
@@ -144,7 +157,6 @@ Nonlinear Path Constraints
144157
Name Location (initial or final) Lower Upper
145158
============ ============================== ============ ==============
146159
tau engine throttle parameter 0.01 1.0
147-
alt_rate climb rate -3000 ft/min 3000 ft/min
148160
============ ============================== ============ ==============
149161

150162
Nonlinear Boundary Constraints

dymos/docs/examples/double_integrator.rst

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ in the control.
3030
1. The ODE System: double_integrator_ode.py
3131
-------------------------------------------
3232

33+
This problem is unique in that we don't actually have to calculate anything in the ODE. For the
34+
sake of Dymos, we create an ExplicitComponent and provide it with the `num_nodes` option, but
35+
it has no inputs and no outputs. The rates for the states are entirely provided by the states
36+
and controls.
37+
3338
.. embed-code::
3439
dymos.examples.double_integrator.double_integrator_ode
3540
:layout: code

dymos/examples/aircraft_steady_flight/aircraft_ode.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
@declare_state('range', rate_source='range_rate_comp.dXdt:range', units='m')
1919
@declare_state('mass_fuel', targets=['mass_comp.mass_fuel'],
2020
rate_source='propulsion.dXdt:mass_fuel', units='kg')
21-
@declare_parameter('alt', targets=['atmos.h', 'aero.alt', 'propulsion.alt'], units='m')
21+
@declare_state('alt', targets=['atmos.h', 'aero.alt', 'propulsion.alt'],
22+
rate_source='climb_rate', units='m')
23+
# @declare_parameter('alt', targets=['atmos.h', 'aero.alt', 'propulsion.alt'], units='m')
2224
@declare_parameter('climb_rate', targets=['gam_comp.climb_rate'], units='m/s')
2325
@declare_parameter('mach', targets=['tas_comp.mach', 'aero.mach'], units='m/s')
2426
@declare_parameter('S', targets=['aero.S', 'flight_equilibrium.S', 'propulsion.S'], units='m**2')

dymos/examples/aircraft_steady_flight/ex_aircraft_steady_flight.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
2727
p.driver.opt_settings["Linesearch tolerance"] = 0.10
2828
p.driver.opt_settings['iSumm'] = 6
2929

30-
num_seg = 15
30+
num_seg = 20
3131
seg_ends, _ = lgl(num_seg + 1)
3232

3333
phase = Phase(transcription,
@@ -53,12 +53,11 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
5353
defect_scaler=1.0E-2)
5454
phase.set_state_options('mass_fuel', units='lbm', fix_initial=True, fix_final=True,
5555
upper=1.5E5, lower=0.0, scaler=1.0E-5, defect_scaler=1.0E-1)
56+
phase.set_state_options('alt', units='kft', fix_initial=True, fix_final=True, lower=0.0,
57+
upper=60)
5658

57-
phase.add_control('alt', units='kft', opt=True, lower=0.0, upper=50.0,
58-
rate_param='climb_rate',
59-
rate_continuity=True, rate_continuity_scaler=1.0,
60-
rate2_continuity=True, rate2_continuity_scaler=1.0, ref=1.0,
61-
fix_initial=True, fix_final=True)
59+
phase.add_control('climb_rate', units='ft/min', opt=True, lower=-3000, upper=3000, ref0=-3000,
60+
ref=3000)
6261

6362
phase.add_control('mach', units=None, opt=False)
6463

@@ -67,7 +66,6 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
6766
phase.add_input_parameter('mass_payload', units='kg')
6867

6968
phase.add_path_constraint('propulsion.tau', lower=0.01, upper=1.0)
70-
phase.add_path_constraint('alt_rate', units='ft/min', lower=-3000, upper=3000, ref=3000)
7169

7270
p.model.connect('assumptions.S', 'phase0.input_parameters:S')
7371
p.model.connect('assumptions.mass_empty', 'phase0.input_parameters:mass_empty')
@@ -83,9 +81,9 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
8381
p['phase0.t_duration'] = 3600.0
8482
p['phase0.states:range'] = phase.interpolate(ys=(0, 1000.0), nodes='state_input')
8583
p['phase0.states:mass_fuel'] = phase.interpolate(ys=(30000, 0), nodes='state_input')
84+
p['phase0.states:alt'][:] = 10.0
8685

8786
p['phase0.controls:mach'][:] = 0.8
88-
p['phase0.controls:alt'][:] = 10.0
8987

9088
p['assumptions.S'] = 427.8
9189
p['assumptions.mass_empty'] = 0.15E6
@@ -101,9 +99,9 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
10199
plt.suptitle('altitude vs time')
102100
plt.figure()
103101
plt.plot(phase.get_values('time', nodes='all'),
104-
phase.get_values('alt_rate', nodes='all', units='ft/min'), 'ro')
105-
plt.plot(exp_out.get_values('time'), exp_out.get_values('alt_rate', units='ft/min'), 'b-')
106-
plt.suptitle('altitude rate vs time')
102+
phase.get_values('climb_rate', nodes='all', units='ft/min'), 'ro')
103+
plt.plot(exp_out.get_values('time'), exp_out.get_values('climb_rate', units='ft/min'), 'b-')
104+
plt.suptitle('climb rate vs time')
107105
plt.figure()
108106
plt.plot(phase.get_values('time', nodes='all'), phase.get_values('mass_fuel', nodes='all'),
109107
'ro')
@@ -131,10 +129,10 @@ def ex_aircraft_steady_flight(optimizer='SLSQP', transcription='gauss-lobatto'):
131129
print(phase.get_values('alt', nodes='all').T)
132130

133131
print('alt_rate')
134-
print(phase.get_values('alt_rate', nodes='all').T)
132+
print(phase.get_values('climb_rate', nodes='all').T)
135133

136-
print('alt_rate2')
137-
print(phase.get_values('alt_rate2', nodes='all').T)
134+
print('climb_rate_rate')
135+
print(phase.get_values('climb_rate_rate', nodes='all').T)
138136

139137
print('range')
140138
print(phase.get_values('range', nodes='all').T)

dymos/examples/aircraft_steady_flight/test/test_aircraft_cruise.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ def test_cruise_results(self):
5858
defect_scaler=0.01)
5959
phase.set_state_options('mass_fuel', fix_final=True, upper=20000.0, lower=0.0,
6060
scaler=1.0E-4, defect_scaler=1.0E-2)
61-
62-
phase.add_control('alt', units='km', opt=False, rate_param='climb_rate')
61+
phase.set_state_options('alt', units='km', fix_initial=True)
6362

6463
phase.add_control('mach', units=None, opt=False)
64+
phase.add_control('climb_rate', units='m/s', opt=False)
6565

6666
phase.add_input_parameter('S', units='m**2')
6767
phase.add_input_parameter('mass_empty', units='kg')
@@ -84,8 +84,9 @@ def test_cruise_results(self):
8484
p['phase0.t_duration'] = 1.515132 * 3600.0
8585
p['phase0.states:range'] = phase.interpolate(ys=(0, 1296.4), nodes='state_input')
8686
p['phase0.states:mass_fuel'] = phase.interpolate(ys=(12236.594555, 0), nodes='state_input')
87+
p['phase0.states:alt'] = 5.0
8788
p['phase0.controls:mach'] = 0.8
88-
p['phase0.controls:alt'] = 5.0
89+
p['phase0.controls:climb_rate'] = 0.0
8990

9091
p['assumptions.S'] = 427.8
9192
p['assumptions.mass_empty'] = 0.15E6

dymos/examples/aircraft_steady_flight/test/test_doc_aircraft_steady_flight.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,18 @@ def test_steady_aircraft_for_docs(self):
6262
phase.set_state_options('mass_fuel', units='lbm', fix_initial=True, fix_final=True,
6363
upper=1.5E5, lower=0.0, scaler=1.0E-5, defect_scaler=1.0E-1)
6464

65-
phase.add_control('alt', units='kft', opt=True, lower=0.0, upper=50.0,
66-
rate_param='climb_rate',
67-
rate_continuity=True, rate_continuity_scaler=1.0,
68-
rate2_continuity=True, rate2_continuity_scaler=1.0, ref=1.0,
69-
fix_initial=True, fix_final=True)
65+
phase.set_state_options('alt', units='kft', fix_initial=True, fix_final=True,
66+
upper=60, lower=0)
67+
68+
phase.add_control('climb_rate', units='ft/min', opt=True, lower=-3000, upper=3000,
69+
ref0=-3000, ref=3000)
7070

7171
phase.add_input_parameter('mach', units=None)
7272
phase.add_input_parameter('S', units='m**2')
7373
phase.add_input_parameter('mass_empty', units='kg')
7474
phase.add_input_parameter('mass_payload', units='kg')
7575

7676
phase.add_path_constraint('propulsion.tau', lower=0.01, upper=1.0)
77-
phase.add_path_constraint('alt_rate', units='ft/min', lower=-3000, upper=3000, ref=3000)
7877

7978
p.model.connect('assumptions.mach', 'phase0.input_parameters:mach')
8079
p.model.connect('assumptions.S', 'phase0.input_parameters:S')
@@ -91,8 +90,7 @@ def test_steady_aircraft_for_docs(self):
9190
p['phase0.t_duration'] = 3600.0
9291
p['phase0.states:range'] = phase.interpolate(ys=(0, 1000.0), nodes='state_input')
9392
p['phase0.states:mass_fuel'] = phase.interpolate(ys=(30000, 0), nodes='state_input')
94-
95-
p['phase0.controls:alt'][:] = 10.0
93+
p['phase0.states:alt'][:] = 10.0
9694

9795
p['assumptions.mach'][:] = 0.8
9896
p['assumptions.S'] = 427.8
@@ -122,10 +120,10 @@ def test_steady_aircraft_for_docs(self):
122120

123121
plt.figure()
124122
plt.plot(phase.get_values('time', nodes='all'),
125-
phase.get_values('alt_rate', nodes='all', units='ft/min'),
123+
phase.get_values('climb_rate', nodes='all', units='ft/min'),
126124
'ro')
127125
plt.plot(exp_out.get_values('time'),
128-
exp_out.get_values('alt_rate', units='ft/min'),
126+
exp_out.get_values('climb_rate', units='ft/min'),
129127
'b-')
130128

131129
plt.suptitle('Climb Rate vs Time')

dymos/examples/aircraft_steady_flight/test/test_ex_aircraft_steady_flight.py

-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import os
44
import unittest
55

6-
import matplotlib
7-
matplotlib.use('Agg')
8-
96
from openmdao.utils.assert_utils import assert_rel_error
107

118
from dymos.examples.aircraft_steady_flight.ex_aircraft_steady_flight import \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from __future__ import print_function, division, absolute_import
2+
3+
import numpy as np
4+
from openmdao.api import ExplicitComponent
5+
from dymos import declare_time, declare_state, declare_parameter
6+
7+
8+
@declare_time(units='s')
9+
@declare_state('pos', rate_source='pos_dot', units='m', shape=(2,))
10+
@declare_state('v', rate_source='vdot', targets=['v'], units='m/s')
11+
@declare_parameter('theta', targets=['theta'], units='rad')
12+
@declare_parameter('g', units='m/s**2', targets=['g'])
13+
class BrachistochroneVectorStatesODE(ExplicitComponent):
14+
15+
def initialize(self):
16+
self.options.declare('num_nodes', types=int)
17+
18+
def setup(self):
19+
nn = self.options['num_nodes']
20+
21+
# Inputs
22+
self.add_input('v', val=np.zeros(nn), desc='velocity', units='m/s')
23+
24+
self.add_input('g', val=9.80665 * np.ones(nn), desc='grav. acceleration', units='m/s/s')
25+
26+
self.add_input('theta', val=np.zeros(nn), desc='angle of wire', units='rad')
27+
28+
self.add_output('pos_dot', val=np.zeros((nn, 2)), desc='velocity components', units='m/s')
29+
30+
self.add_output('vdot', val=np.zeros(nn), desc='acceleration magnitude', units='m/s**2')
31+
32+
self.add_output('check', val=np.zeros(nn), desc='check solution: v/sin(theta) = constant',
33+
units='m/s')
34+
35+
# Setup partials
36+
arange = np.arange(self.options['num_nodes'])
37+
38+
self.declare_partials(of='vdot', wrt='g', rows=arange, cols=arange)
39+
self.declare_partials(of='vdot', wrt='theta', rows=arange, cols=arange)
40+
41+
rs = np.arange(2*nn, dtype=int)
42+
cs = np.repeat(np.arange(nn, dtype=int), 2)
43+
self.declare_partials(of='pos_dot', wrt='v', rows=rs, cols=cs, val=5)
44+
self.declare_partials(of='pos_dot', wrt='theta', rows=rs, cols=cs, val=7)
45+
46+
self.declare_partials(of='check', wrt='v', rows=arange, cols=arange)
47+
self.declare_partials(of='check', wrt='theta', rows=arange, cols=arange)
48+
49+
def compute(self, inputs, outputs):
50+
theta = inputs['theta']
51+
cos_theta = np.cos(theta)
52+
sin_theta = np.sin(theta)
53+
g = inputs['g']
54+
v = inputs['v']
55+
56+
outputs['vdot'] = g * cos_theta
57+
outputs['pos_dot'][:, 0] = v * sin_theta
58+
outputs['pos_dot'][:, 1] = -v * cos_theta
59+
outputs['check'] = v / sin_theta
60+
61+
def compute_partials(self, inputs, jacobian):
62+
theta = inputs['theta']
63+
cos_theta = np.cos(theta)
64+
sin_theta = np.sin(theta)
65+
g = inputs['g']
66+
v = inputs['v']
67+
68+
jacobian['vdot', 'g'] = cos_theta
69+
jacobian['vdot', 'theta'] = -g * sin_theta
70+
71+
jacobian['pos_dot', 'v'][0::2] = sin_theta
72+
jacobian['pos_dot', 'v'][1::2] = -cos_theta
73+
74+
jacobian['pos_dot', 'theta'][0::2] = v * cos_theta
75+
jacobian['pos_dot', 'theta'][1::2] = v * sin_theta
76+
77+
jacobian['check', 'v'] = 1 / sin_theta
78+
jacobian['check', 'theta'] = -v * cos_theta / sin_theta**2

0 commit comments

Comments
 (0)