Skip to content

Commit 623c785

Browse files
committed
mission and optimization outline test
1 parent 5b8e964 commit 623c785

File tree

2 files changed

+383
-0
lines changed

2 files changed

+383
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
## Mission Solver Structure
2+
3+
This is a high level overview of how the mission solver functions. The purpose is to show the structure that is used for an existing mission, and show where changes should be made if different functionality is desired.
4+
5+
### File Structure
6+
7+
Mission scripts are split into two folders in the SUAVE repository. The first is in trunk/SUAVE/**Analyses/Mission**/Segments, and the second is in trunk/SUAVE/**Methods/Missions**/Segments. As with other types of analyses and methods, the distinction between these is that the Analyses folder contains classes that are built to use functions stored in the Methods folder. This division is done to make it easier to build new analysis classes using a mix of available methods.
8+
9+
A typical mission segment analysis file contains four keys parts. The first specifies default user inputs, unknowns, and residuals. The inputs are used to provide the analysis with conditions that need to be met, while the unknowns and residuals are used as part of the solution process. The second sets the initialization functions for the analysis, which are run at the beginning. The third picks the convergence method and specifies the functions that will be used during iteration. The fourth finalizes the data and processes it for results output.
10+
11+
### Initialization
12+
13+
For this tutorial, we will be considering the constant speed constant altitude cruise segment. The files are available [here (Analysis)](https://github.com/suavecode/SUAVE/blob/develop/trunk/SUAVE/Analyses/Mission/Segments/Cruise/Constant_Speed_Constant_Altitude.py) and [here (Method)](https://github.com/suavecode/SUAVE/blob/develop/trunk/SUAVE/Methods/Missions/Segments/Cruise/Constant_Speed_Constant_Altitude.py). This class also inherits information from more general segment classes, which include many of the processing functions. As with other segments, the user will specify key conditions. For this case, altitude, air speed, and distance are the necessary inputs. If the user does not specify an altitude, it will be taken automatically from the last value in the previous segment. These inputs must be specified in some way for the mission segment to be evaluated. They are shown below as well:
14+
15+
self.altitude = None
16+
self.air_speed = 10. * Units['km/hr']
17+
self.distance = 10. * Units.km
18+
19+
The other set of segment specific initial values are the values used for solving the segment (typically this means satisfying a force balance at every evaluation point). These can be changed by the user if needed, but the default values should perform fine for most cases.
20+
21+
self.state.unknowns.throttle = ones_row(1) * 0.5
22+
self.state.unknowns.body_angle = ones_row(1) * 0.0
23+
self.state.residuals.forces = ones_row(2) * 0.0
24+
25+
Here throttle and body angle are the unknowns, and the values shown here are the values they will start at. The residuals will be computed based on these unknowns, so their initial value is not important. Instead they are initialized just to create the necessary data structure. The ones_row line will create a numpy array with the number of elements needed for evaluation.
26+
27+
### Evaluation Details
28+
29+
Most of the missions in SUAVE, including this one, are broken into several points in time based on a Chebyshev polynomial. This causes the points to be closer together at either end of the segment. The choice of a Chebyshev polynomial (which creates cosine spacing) provides better convergence and smoothness properties versus other methods such as linear spacing.
30+
31+
<img src="http://suave.stanford.edu/images/drag_components_2.png" width="800" height="234" />
32+
33+
At each of these points the aerodynamic analysis is queried to find CL and CD, which are then converted to lift and drag. These values will be dependent on the body angle unknown and other aerodynamic parameters. Thrust is found from the vehicle's energy network, which is dependent on the throttle unknown. A weight is determined by looking at the initial weight and subsequent mass rate (typically corresponding with fuel burn). In this cruise segment, these forces are summed in 2D and the results are put in the residuals. The functions needed to arrive these forces are found in the Update Conditions section of the [Analysis file](dox_link). This section is also shown below in one of the steps to create a new mission.
34+
35+
Once the evaluation process has been performed at all points, the unknowns and residuals are fed back to the solve routine, which in this case is scipy's fsolve. The file that performs this process is [here](https://github.com/suavecode/SUAVE/blob/develop/trunk/SUAVE/Methods/Missions/Segments/converge_root.py). This routine continues evaluating the points until convergence is reached. Once this happens, post processing is done to put the data in the results output.
36+
37+
### Using Multiple Segments
38+
39+
Multiple segments can be run sequentially by appending them in the desired order. Examples of this are in all the tutorial files that have an aircraft fly a full mission. In addition, the full mission can be run simultaneously will all segment constraints used together. If you are interested in doing something like this, please ask us about it on our [forum](/forum).
40+
41+
#### Process Summary
42+
43+
Mission Setup
44+
45+
* Initializes default values for unknowns
46+
* Initializes set of functions used to determine residuals
47+
* Reads user input for segment parameters
48+
* Adds the analysis group to be used (including the vehicle and items like atmosphere)
49+
* Appends segments in order
50+
51+
Evaluate
52+
53+
* Varies unknowns until residual convergence is reached using scipy's fsolve
54+
* Repeats process for each segment until full mission is complete
55+
56+
### Adding New Mission Segments
57+
58+
The segment described above uses two unknowns to solve force residuals in two dimensions. This general setup works well for many problems of interest, but SUAVE is designed to accommodate other mission analysis types as well. A user may want to add control surface deflection and solve for moments as well, or look at forces in all three dimensions.
59+
60+
In addition, a user may want to modify how the mission is flown, as is done with the many other segments currently available. They may want to modify how the mission is solved, such as is done in our single point evaluation segments where finite differencing is not relevant.
61+
62+
Here we will explain the process of modifying our constant speed constant rate climb segment to be constant throttle constant speed. This still uses 2D force balance but changes the profile. There are four functions that are modified here. The first is shown below. The functions can be found in [here]() and [here]()
63+
64+
def initialize_conditions(segment,state):
65+
66+
# unpack
67+
climb_rate = segment.climb_rate
68+
air_speed = segment.air_speed
69+
alt0 = segment.altitude_start
70+
altf = segment.altitude_end
71+
t_nondim = state.numerics.dimensionless.control_points
72+
conditions = state.conditions
73+
74+
# check for initial altitude
75+
if alt0 is None:
76+
if not state.initials: raise AttributeError('initial altitude not set')
77+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
78+
79+
# discretize on altitude
80+
alt = t_nondim * (altf-alt0) + alt0
81+
82+
# process velocity vector
83+
v_mag = air_speed
84+
v_z = -climb_rate # z points down
85+
v_x = np.sqrt( v_mag**2 - v_z**2 )
86+
87+
# pack conditions
88+
conditions.frames.inertial.velocity_vector[:,0] = v_x
89+
conditions.frames.inertial.velocity_vector[:,2] = v_z
90+
conditions.frames.inertial.position_vector[:,2] = -alt[:,0] # z points down
91+
conditions.freestream.altitude[:,0] = alt[:,0] # positive altitude in this context
92+
93+
This function initializes speed and altitude based on the given climb rate, airspeed, and altitude end points. t_nondim gives nondimensional time in cosine spacing from 0 to 1 in order to pick the values at the points to be evaluated. Unfortunately, when we use constant throttle we cannot know beforehand exactly how altitude (or climb rate in this case) will vary with time, so altitude cannot be spaced with this method. Instead a different function is used to initialize conditions:
94+
95+
def initialize_conditions(segment,state):
96+
97+
# unpack
98+
throttle = segment.throttle
99+
air_speed = segment.air_speed
100+
alt0 = segment.altitude_start
101+
altf = segment.altitude_end
102+
t_nondim = state.numerics.dimensionless.control_points
103+
conditions = state.conditions
104+
105+
# check for initial altitude
106+
if alt0 is None:
107+
if not state.initials: raise AttributeError('initial altitude not set')
108+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
109+
110+
# pack conditions
111+
conditions.propulsion.throttle[:,0] = throttle
112+
conditions.frames.inertial.velocity_vector[:,0] = air_speed # start up value
113+
114+
Here only the throttle and air speed are loaded in, and discretization of other values will need to occur later so that it is part of the iteration loop. This requires a new function that updates the altitude differentials.
115+
116+
def update_differentials_altitude(segment,state):
117+
118+
# unpack
119+
t = state.numerics.dimensionless.control_points
120+
D = state.numerics.dimensionless.differentiate
121+
I = state.numerics.dimensionless.integrate
122+
123+
124+
# Unpack segment initials
125+
alt0 = segment.altitude_start
126+
altf = segment.altitude_end
127+
conditions = state.conditions
128+
129+
r = state.conditions.frames.inertial.position_vector
130+
v = state.conditions.frames.inertial.velocity_vector
131+
132+
# check for initial altitude
133+
if alt0 is None:
134+
if not state.initials: raise AttributeError('initial altitude not set')
135+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
136+
137+
# get overall time step
138+
vz = -v[:,2,None] # Inertial velocity is z down
139+
dz = altf- alt0
140+
dt = dz / np.dot(I[-1,:],vz)[-1] # maintain column array
141+
142+
# Integrate vz to get altitudes
143+
alt = alt0 + np.dot(I*dt,vz)
144+
145+
# rescale operators
146+
t = t * dt
147+
148+
# pack
149+
t_initial = state.conditions.frames.inertial.time[0,0]
150+
state.conditions.frames.inertial.time[:,0] = t_initial + t[:,0]
151+
conditions.frames.inertial.position_vector[:,2] = -alt[:,0] # z points down
152+
conditions.freestream.altitude[:,0] = alt[:,0] # positive altitude in this context
153+
154+
return
155+
156+
In this function, t, D, and I are numpy arrays that allow approximate differentiation and integration. Since the total time is not known without determining the climb rate, we must first determine the time required to reach the final altitude. The line `dt = dz / np.dot(I[-1,:],vz)[-1]` does this with the integrator providing the amount of altitude gained if the velocities were spread across just one second instead of the full segment time. This gives the scaling quantity `dt` that is then used to get the altitude at every point in `alt = alt0 + np.dot(I*dt,vz)`. The values for altitude are then are then packed for use in other functions.
157+
158+
The above allows us to deal with discretization without a known profile, but we also must calculate the velocity in order to use this. This is done with another added function.
159+
160+
def update_velocity_vector_from_wind_angle(segment,state):
161+
162+
# unpack
163+
conditions = state.conditions
164+
v_mag = segment.air_speed
165+
alpha = state.unknowns.wind_angle[:,0][:,None]
166+
theta = state.unknowns.body_angle[:,0][:,None]
167+
168+
# Flight path angle
169+
gamma = theta-alpha
170+
171+
# process
172+
v_x = v_mag * np.cos(gamma)
173+
v_z = -v_mag * np.sin(gamma) # z points down
174+
175+
# pack
176+
conditions.frames.inertial.velocity_vector[:,0] = v_x[:,0]
177+
conditions.frames.inertial.velocity_vector[:,2] = v_z[:,0]
178+
179+
return conditions
180+
181+
This uses our new set of unknowns to determine the velocities.
182+
183+
Additionally, since the unknowns are different we must change the function that unpacks them. Wind angle does not need to be stored so it is not included here.
184+
185+
def unpack_body_angle(segment,state):
186+
187+
# unpack unknowns
188+
theta = state.unknowns.body_angle
189+
190+
# apply unknowns
191+
state.conditions.frames.body.inertial_rotations[:,1] = theta[:,0]
192+
193+
We now add these functions to the segment process list.
194+
195+
# --------------------------------------------------------------
196+
# Initialize - before iteration
197+
# --------------------------------------------------------------
198+
initialize = self.process.initialize
199+
200+
initialize.expand_state = Methods.expand_state
201+
initialize.differentials = Methods.Common.Numerics.initialize_differentials_dimensionless
202+
initialize.conditions = Methods.Climb.Constant_Throttle_Constant_Speed.initialize_conditions
203+
initialize.velocities = Methods.Climb.Constant_Throttle_Constant_Speed.update_velocity_vector_from_wind_angle
204+
initialize.differentials_altitude = Methods.Climb.Constant_Throttle_Constant_Speed.update_differentials_altitude
205+
206+
and
207+
208+
# Unpack Unknowns
209+
iterate.unknowns = Process()
210+
iterate.unknowns.mission = Methods.Climb.Constant_Throttle_Constant_Speed.unpack_body_angle
211+
212+
# Update Conditions
213+
iterate.conditions = Process()
214+
iterate.conditions.velocities = Methods.Climb.Constant_Throttle_Constant_Speed.update_velocity_vector_from_wind_angle
215+
iterate.conditions.differentials_a = Methods.Climb.Constant_Throttle_Constant_Speed.update_differentials_altitude
216+
iterate.conditions.differentials_b = Methods.Common.Numerics.update_differentials_time
217+
iterate.conditions.acceleration = Methods.Common.Frames.update_acceleration
218+
iterate.conditions.altitude = Methods.Common.Aerodynamics.update_altitude
219+
iterate.conditions.atmosphere = Methods.Common.Aerodynamics.update_atmosphere
220+
iterate.conditions.gravity = Methods.Common.Weights.update_gravity
221+
iterate.conditions.freestream = Methods.Common.Aerodynamics.update_freestream
222+
iterate.conditions.orientations = Methods.Common.Frames.update_orientations
223+
iterate.conditions.aerodynamics = Methods.Common.Aerodynamics.update_aerodynamics
224+
iterate.conditions.stability = Methods.Common.Aerodynamics.update_stability
225+
iterate.conditions.propulsion = Methods.Common.Energy.update_thrust
226+
iterate.conditions.weights = Methods.Common.Weights.update_weights
227+
iterate.conditions.forces = Methods.Common.Frames.update_forces
228+
iterate.conditions.planet_position = Methods.Common.Frames.update_planet_position
229+
230+
If you have any questions that are not answered in other tutorials or the FAQ please ask on our [forum](/forum) page. This is also the place to go if you want help building a more elaborate evaluation, such as one that includes moments.

0 commit comments

Comments
 (0)