Skip to content

Commit 59d9118

Browse files
committed
2 parents ae235fe + 5e1189c commit 59d9118

File tree

1,853 files changed

+204276
-3217
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,853 files changed

+204276
-3217
lines changed

_posts/2000-01-04-mission_solver.md

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
---
2+
layout: post
3+
title: Mission Solver Structure
4+
date: 2017-01-07 13:25:00
5+
categories: blog
6+
description: Understanding the mission solver
7+
8+
permalink: /guides/mission.html
9+
---
10+
11+
## Mission Solver Code Structure
12+
13+
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.
14+
15+
### File Structure
16+
17+
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.
18+
19+
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.
20+
21+
### Initialization
22+
23+
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:
24+
25+
self.altitude = None
26+
self.air_speed = 10. * Units['km/hr']
27+
self.distance = 10. * Units.km
28+
29+
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.
30+
31+
self.state.unknowns.throttle = ones_row(1) * 0.5
32+
self.state.unknowns.body_angle = ones_row(1) * 0.0
33+
self.state.residuals.forces = ones_row(2) * 0.0
34+
35+
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.
36+
37+
### Evaluation Details
38+
39+
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.
40+
41+
<img src="http://suave.stanford.edu/images/drag_components_2.png" width="800" height="234" />
42+
43+
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. This section is also shown below in one of the steps to create a new mission.
44+
45+
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.
46+
47+
### Using Multiple Segments
48+
49+
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).
50+
51+
#### Process Summary
52+
53+
Mission Setup
54+
55+
* Initializes default values for unknowns
56+
* Initializes set of functions used to determine residuals
57+
* Reads user input for segment parameters
58+
* Adds the analysis group to be used (including the vehicle and items like atmosphere)
59+
* Appends segments in order
60+
61+
Evaluate
62+
63+
* Varies unknowns until residual convergence is reached using scipy's fsolve
64+
* Repeats process for each segment until full mission is complete
65+
66+
### Adding New Mission Segments
67+
68+
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.
69+
70+
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.
71+
72+
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]()
73+
74+
def initialize_conditions(segment,state):
75+
76+
# unpack
77+
climb_rate = segment.climb_rate
78+
air_speed = segment.air_speed
79+
alt0 = segment.altitude_start
80+
altf = segment.altitude_end
81+
t_nondim = state.numerics.dimensionless.control_points
82+
conditions = state.conditions
83+
84+
# check for initial altitude
85+
if alt0 is None:
86+
if not state.initials: raise AttributeError('initial altitude not set')
87+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
88+
89+
# discretize on altitude
90+
alt = t_nondim * (altf-alt0) + alt0
91+
92+
# process velocity vector
93+
v_mag = air_speed
94+
v_z = -climb_rate # z points down
95+
v_x = np.sqrt( v_mag**2 - v_z**2 )
96+
97+
# pack conditions
98+
conditions.frames.inertial.velocity_vector[:,0] = v_x
99+
conditions.frames.inertial.velocity_vector[:,2] = v_z
100+
conditions.frames.inertial.position_vector[:,2] = -alt[:,0] # z points down
101+
conditions.freestream.altitude[:,0] = alt[:,0] # positive altitude in this context
102+
103+
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:
104+
105+
def initialize_conditions(segment,state):
106+
107+
# unpack
108+
throttle = segment.throttle
109+
air_speed = segment.air_speed
110+
alt0 = segment.altitude_start
111+
altf = segment.altitude_end
112+
t_nondim = state.numerics.dimensionless.control_points
113+
conditions = state.conditions
114+
115+
# check for initial altitude
116+
if alt0 is None:
117+
if not state.initials: raise AttributeError('initial altitude not set')
118+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
119+
120+
# pack conditions
121+
conditions.propulsion.throttle[:,0] = throttle
122+
conditions.frames.inertial.velocity_vector[:,0] = air_speed # start up value
123+
124+
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.
125+
126+
def update_differentials_altitude(segment,state):
127+
128+
# unpack
129+
t = state.numerics.dimensionless.control_points
130+
D = state.numerics.dimensionless.differentiate
131+
I = state.numerics.dimensionless.integrate
132+
133+
134+
# Unpack segment initials
135+
alt0 = segment.altitude_start
136+
altf = segment.altitude_end
137+
conditions = state.conditions
138+
139+
r = state.conditions.frames.inertial.position_vector
140+
v = state.conditions.frames.inertial.velocity_vector
141+
142+
# check for initial altitude
143+
if alt0 is None:
144+
if not state.initials: raise AttributeError('initial altitude not set')
145+
alt0 = -1.0 * state.initials.conditions.frames.inertial.position_vector[-1,2]
146+
147+
# get overall time step
148+
vz = -v[:,2,None] # Inertial velocity is z down
149+
dz = altf- alt0
150+
dt = dz / np.dot(I[-1,:],vz)[-1] # maintain column array
151+
152+
# Integrate vz to get altitudes
153+
alt = alt0 + np.dot(I*dt,vz)
154+
155+
# rescale operators
156+
t = t * dt
157+
158+
# pack
159+
t_initial = state.conditions.frames.inertial.time[0,0]
160+
state.conditions.frames.inertial.time[:,0] = t_initial + t[:,0]
161+
conditions.frames.inertial.position_vector[:,2] = -alt[:,0] # z points down
162+
conditions.freestream.altitude[:,0] = alt[:,0] # positive altitude in this context
163+
164+
return
165+
166+
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.
167+
168+
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.
169+
170+
def update_velocity_vector_from_wind_angle(segment,state):
171+
172+
# unpack
173+
conditions = state.conditions
174+
v_mag = segment.air_speed
175+
alpha = state.unknowns.wind_angle[:,0][:,None]
176+
theta = state.unknowns.body_angle[:,0][:,None]
177+
178+
# Flight path angle
179+
gamma = theta-alpha
180+
181+
# process
182+
v_x = v_mag * np.cos(gamma)
183+
v_z = -v_mag * np.sin(gamma) # z points down
184+
185+
# pack
186+
conditions.frames.inertial.velocity_vector[:,0] = v_x[:,0]
187+
conditions.frames.inertial.velocity_vector[:,2] = v_z[:,0]
188+
189+
return conditions
190+
191+
This uses our new set of unknowns to determine the velocities.
192+
193+
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.
194+
195+
def unpack_body_angle(segment,state):
196+
197+
# unpack unknowns
198+
theta = state.unknowns.body_angle
199+
200+
# apply unknowns
201+
state.conditions.frames.body.inertial_rotations[:,1] = theta[:,0]
202+
203+
We now add these functions to the segment process list.
204+
205+
# --------------------------------------------------------------
206+
# Initialize - before iteration
207+
# --------------------------------------------------------------
208+
initialize = self.process.initialize
209+
210+
initialize.expand_state = Methods.expand_state
211+
initialize.differentials = Methods.Common.Numerics.initialize_differentials_dimensionless
212+
initialize.conditions = Methods.Climb.Constant_Throttle_Constant_Speed.initialize_conditions
213+
initialize.velocities = Methods.Climb.Constant_Throttle_Constant_Speed.update_velocity_vector_from_wind_angle
214+
initialize.differentials_altitude = Methods.Climb.Constant_Throttle_Constant_Speed.update_differentials_altitude
215+
216+
and
217+
218+
# Unpack Unknowns
219+
iterate.unknowns = Process()
220+
iterate.unknowns.mission = Methods.Climb.Constant_Throttle_Constant_Speed.unpack_body_angle
221+
222+
# Update Conditions
223+
iterate.conditions = Process()
224+
iterate.conditions.velocities = Methods.Climb.Constant_Throttle_Constant_Speed.update_velocity_vector_from_wind_angle
225+
iterate.conditions.differentials_a = Methods.Climb.Constant_Throttle_Constant_Speed.update_differentials_altitude
226+
iterate.conditions.differentials_b = Methods.Common.Numerics.update_differentials_time
227+
iterate.conditions.acceleration = Methods.Common.Frames.update_acceleration
228+
iterate.conditions.altitude = Methods.Common.Aerodynamics.update_altitude
229+
iterate.conditions.atmosphere = Methods.Common.Aerodynamics.update_atmosphere
230+
iterate.conditions.gravity = Methods.Common.Weights.update_gravity
231+
iterate.conditions.freestream = Methods.Common.Aerodynamics.update_freestream
232+
iterate.conditions.orientations = Methods.Common.Frames.update_orientations
233+
iterate.conditions.aerodynamics = Methods.Common.Aerodynamics.update_aerodynamics
234+
iterate.conditions.stability = Methods.Common.Aerodynamics.update_stability
235+
iterate.conditions.propulsion = Methods.Common.Energy.update_thrust
236+
iterate.conditions.weights = Methods.Common.Weights.update_weights
237+
iterate.conditions.forces = Methods.Common.Frames.update_forces
238+
iterate.conditions.planet_position = Methods.Common.Frames.update_planet_position
239+
240+
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)