Skip to content

Commit cbe423d

Browse files
authored
Merge pull request #25 from Richard-L-Zhang/Variable_Cd
Variable parachute drag coefficients
2 parents fa1910d + c960126 commit cbe423d

File tree

13 files changed

+2040
-262
lines changed

13 files changed

+2040
-262
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ output.json
2626
*.grib
2727
*.NC
2828
*.anl
29+
data/pyro.json

campyros/main.py

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
11
"""
22
Contains the classes and functions for the core trajectory simulation. SI units unless stated otherwise.
3-
43
Notes
54
-----
6-
75
Known issues:
8-
96
- Unsure about the use of "dx" in "scipy.misc.derivative(self.mass_model.mass, time, dx=1)" when calculating mdot
107
- Possible inconsistency in the definition of the launch site coordinate system, and whether the origin is at alt=0 or alt=launch_site.alt. I haven't thoroughly checked for this inconsistency yet.
11-
128
Coordinate systems:
13-
149
- Body (x_b, y_b, z_b)
1510
- Origin on rocket
1611
- Rotates with the rocket.
17-
1812
- y points east and z north at take off (before rail alignment is accounted for) x up.
1913
- x is along the "long" axis of the rocket.
2014
- Launch site (x_l, y_l, z_l):
2115
- Origin has the launch site's longitude and latitude, but is at altitude = 0.
2216
- Rotates with the Earth.
23-
2417
- z points up (normal to the surface of the Earth).
2518
- y points East (tangentially to the surface of the Earth).
2619
- x points South (tangentially to the surface of the Earth).
2720
- Inertial (x_i, y_i, z_i):
2821
- Origin at centre of the Earth.
2922
- Does not rotate.
30-
3123
- z points to North from the centre of Earth.
3224
- x aligned with launch site at start .
3325
- y defined from x and z (so it is a right hand coordinate system).
@@ -46,7 +38,8 @@
4638
import pandas as pd
4739
from metpy.units import units
4840

49-
import scipy.interpolate, scipy.misc
41+
import scipy.interpolate as interpolate
42+
import scipy.misc
5043
import scipy.integrate as integrate
5144
from scipy.spatial.transform import Rotation
5245
import numexpr as ne
@@ -70,22 +63,20 @@
7063
from .wind import Wind
7164

7265
__copyright__ = """
73-
7466
Copyright 2021 Jago Strong-Wright & Daniel Gibbons
75-
7667
This program is free software: you can redistribute it and/or modify
7768
it under the terms of the GNU General Public License as published by
7869
the Free Software Foundation, either version 3 of the License, or
7970
(at your option) any later version.
80-
8171
This program is distributed in the hope that it will be useful,
8272
but WITHOUT ANY WARRANTY; without even the implied warranty of
8373
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8474
GNU General Public License for more details.
85-
8675
You should have received a copy of the GNU General Public License
8776
along with this program. If not, see <https://www.gnu.org/licenses/>.
8877
78+
Modifcations:
79+
- Richard Zhang: Mach varying parachute drag coefficients
8980
"""
9081

9182
# print("""<name tbc> Copyright (C) 2021 Jago Strong-Wright & Daniel Gibbons
@@ -94,15 +85,13 @@
9485

9586
def warning_on_one_line(message, category, filename, lineno, file=None, line=None):
9687
"""A one line warning format
97-
9888
Args:
9989
message ([type]): [description]
10090
category ([type]): [description]
10191
filename ([type]): [description]
10292
lineno ([type]): [description]
10393
file ([type], optional): [description]. Defaults to None.
10494
line ([type], optional): [description]. Defaults to None.
105-
10695
Returns:
10796
[type]: [description]
10897
"""
@@ -118,60 +107,89 @@ def __init__(
118107
):
119108
"""
120109
Object holding the parachute information
121-
122110
Note
123111
----
124112
The parachute model does not currently simulate the full rotational dynamics of the rocket.
125113
Instead it orientates the rocket such that it is "rear first into the wind" (as intuition would suggest).
126114
This is due to problems trying to model the parachute exerting torque on the body, possibly because it has to flip
127115
the rocket over at apogee
128-
129116
Args:
130117
main_s (float): Area of main chute (m^2)
131-
main_c_d (float): Drag coefficient for the main parachute (m^2)
132118
drogue_s (float): Area of the main parachute (m^2)
133-
drogue_c_d (float): Drag coefficient for the drogue chute
134119
main_alt (float): Altitude at which the main parachute deploys (m)
120+
parachute_Cd: pandas.dataframe object, read from Parachute_data.CSV
135121
attach_distance (float, optional): Distance between the rocket nose tip and the parachute attachment point (m). Defaults to 0.0.
136-
122+
main_cd_data (tuple): tuple of 2 np arrays, of mach number and Main Cd
123+
dro_cd_data (tuple): tuple of 2 np arrays, of mach number and Drogue Cd
137124
Attributes:
138125
main_s (float): Area of main chute (m^2)
139-
main_c_d (float): Drag coefficient for the main parachute (m^2)
140126
drogue_s (float): Area of the main parachute (m^2)
141-
drogue_c_d (float): Drag coefficient for the drogue chute
142127
main_alt (float): Altitude at which the main parachute deploys (m)
143128
attach_distance (float, optional): Distance between the rocket nose tip and the parachute attachment point (m). Defaults to 0.0.
129+
main_cd_data (tuple): tuple of 2 np arrays, of mach number and Main Cd
130+
dro_cd_data (tuple): tuple of 2 np arrays, of mach number and Drogue Cd
131+
dro_cd(Mach) (interp1d object): give Drogue Cd based on Mach
132+
main_cd(Mach) (interp1d object): give Main Cd based on Mach
144133
"""
145134
self.main_s = main_s
146-
self.main_c_d = main_c_d
147135
self.drogue_s = drogue_s
148-
self.drogue_c_d = drogue_c_d
149-
150136
self.main_alt = main_alt
151137
self.attach_distance = attach_distance
152138

153-
def get(self, alt):
154-
"""Returns the drag coefficient and area of the parachute, given the current altitude.
155-
I.e., it checks if the main or drogue parachute is open, and returns the relevant values.
139+
# get Drogue and Main Cd data, note bound error. Support tuple/list of np array(s)
140+
if isinstance(main_c_d, float) or isinstance(main_c_d, int):
141+
self.variable_main_c_d = False
142+
self.main_c_d = main_c_d
143+
elif len(main_c_d) == 2: # list, tuple, np.array
144+
self.variable_main_c_d = True
145+
self.main_c_d = interpolate.interp1d(
146+
main_c_d[0],
147+
main_c_d[1],
148+
copy=True,
149+
bounds_error=False,
150+
fill_value=(0, main_c_d[0][-1]),
151+
)
152+
if isinstance(drogue_c_d, float) or isinstance(drogue_c_d, int):
153+
self.variable_drogue_c_d = False
154+
self.drogue_c_d = drogue_c_d
155+
elif len(drogue_c_d) == 2: # list, tuple, np.array
156+
self.variable_drogue_c_d = True
157+
self.drogue_cd = interpolate.interp1d(
158+
drogue_c_d[0],
159+
drogue_c_d[1],
160+
copy=True,
161+
bounds_error=False,
162+
fill_value=(0, drogue_c_d[0][-1]),
163+
)
156164

165+
def get(self, alt, mach):
166+
"""Returns the drag coefficient and area of the parachute, given the current altitude and Mach Number.
167+
I.e., it checks if the main or drogue parachute is open, and returns the relevant values.
157168
Args:
158169
alt (float): Current altitude (m)
159-
170+
mach (float): Mach number
160171
Returns:
161172
float, float: Drag coefficient, parachute area (m^2)
162173
"""
174+
# if Mach < 0 or Mach > 7:
175+
# print("Mach Number is {}, out of range!".format(Mach))
163176
if alt < self.main_alt:
164-
c_d = self.main_c_d
165177
s = self.main_s
178+
if self.variable_main_c_d == False:
179+
c_d = self.main_c_d
180+
elif self.variable_main_c_d == True:
181+
c_d = self.main_c_d(mach)
166182
else:
167-
c_d = self.drogue_c_d
168183
s = self.drogue_s
184+
if self.variable_main_c_d == False:
185+
c_d = self.drogue_c_d
186+
elif self.variable_main_c_d == True:
187+
c_d = self.drogue_cd(mach)
169188
return c_d, s
170189

171190

172191
class LaunchSite:
173192
"""Object for holding launch site information.
174-
175193
Args:
176194
rail_length (float): Length of the launch rail (m)
177195
rail_yaw (float): Yaw angle of the launch rail (deg), using a right-hand rotation rule out the launch frame z-axis. "rail_yaw = 0" points South, "rail_yaw = 90" points East.
@@ -186,7 +204,6 @@ class LaunchSite:
186204
forcast_time (str, optional): Forcast run time, must be "00", "06", "12" or "18". Defaults to "00".
187205
forcast_plus_time (str, optional): Hours forcast forward from forcast time, must be three digits between 000 and 123 (?). Defaults to "000".
188206
fast_wind (bool, optional): ???. Defaults to False.
189-
190207
Attributes:
191208
rail_length (float): Length of the launch rail (m)
192209
rail_yaw (float): Yaw angle of the launch rail (deg), using a right-hand rotation rule out the launch frame z-axis. "rail_yaw = 0" points South, "rail_yaw = 90" points East.
@@ -234,7 +251,6 @@ def __init__(
234251

235252
class Rocket:
236253
"""Rocket object to contain rocket data and run rocketry simulations.
237-
238254
Args:
239255
mass_model (MassModel): MassModel object containing all the data on mass and moments of inertia.
240256
motor (Motor): Motor object containing information on the rocket engine.
@@ -245,10 +261,9 @@ class Rocket:
245261
rtol (float, optional): Relative error tolerance for integration. Defaults to 1e-7.
246262
atol (float, optional): Absolute error tolerance for integration. Defaults to 1e-14.
247263
parachute (Parachute, optional): Parachute object, containing parachute data. Defaults to Parachute(0,0,0,0,0,0).
248-
alt_poll_interval (int, optional): ???. Defaults to 1.
264+
alt_poll_interval (int, optional): How often to check for parachute opening. Defaults to 1.
249265
thrust_vector (array, optional): Direction of thrust in body coordinates. Defaults to np.array([1,0,0]).
250266
errors (dict, optional): Multiplication factors for the gravity, pressure, density and speed of sound. Used in the statistics model. Defaults to {"gravity":1.0,"pressure":1.0,"density":1.0,"speed_of_sound":1.0}.
251-
252267
Attributes:
253268
mass_model (MassModel): MassModel object containing all the data on mass and moments of inertia.
254269
motor (Motor): Motor object containing information on the rocket engine.
@@ -271,9 +286,9 @@ class Rocket:
271286
alt (float): Rocket altitude (m).
272287
on_rail (bool): True if the rocket is still on the rail, False if the rocket is off the rail.
273288
burn_out (bool): False if engine is still firing, True if the engine has finished firing.
274-
alt_record(???) : ???.
275-
alt_poll_watch_interval (???) : ???
276-
alt_poll_watch (???): ???
289+
alt_record(float) : "Current" altitude of the rocket used to check for parahute opening.
290+
alt_poll_watch_interval (float) : How often to check if the parachute needs to be openes (s).
291+
alt_poll_watch (float): Last polled time.
277292
"""
278293

279294
def __init__(
@@ -360,11 +375,9 @@ def __init__(
360375

361376
def fdot(self, time, fn):
362377
"""Returns the rate of change of the rocket's state array, 'fn'.
363-
364378
Args:
365379
time (float): Time since ignition (s).
366380
fn (array): Rocket's current state, [pos_i[0], pos_i[1], pos_i[2], vel_i[0], vel_i[1], vel_i[2], w_b[0], w_b[1], w_b[2], xb_i[0], xb_i[1], xb_i[2], yb_i[0], yb_i[1], yb_i[2], zb_i[0],zb_i[1],zb_i[2]]
367-
368381
Returns:
369382
array: Rate of change of fdot, i.e. [vel_i[0], vel_i[1], vel_i[2], acc_i[0], acc_i[1], acc_i[2], wdot_b[0], wdot_b[1], wdot_b[2], xbdot[0], xbdot[1], xbdot[2], ybdot[0], ybdot[1], ybdot[2], zbdot[0], zbdot[1], zbdot[2]]
370383
"""
@@ -438,18 +451,18 @@ def fdot(self, time, fn):
438451
v_relative_wind_b = b2i.inv().apply(v_relative_wind_i)
439452
air_speed = np.linalg.norm(v_relative_wind_b)
440453
q = 0.5 * ambient_density * air_speed ** 2 # Dynamic pressure
454+
mach = air_speed / speed_of_sound
441455

442456
if self.parachute_deployed == True and self.parachute.main_c_d != 0:
443457
# Parachute forces
444-
CD, ref_area = self.parachute.get(alt)
458+
CD, ref_area = self.parachute.get(alt, mach)
445459
F_parachute_i = -0.5 * q * ref_area * CD * v_relative_wind_i / air_speed
446460

447461
# Append to list of forces
448462
F_i = F_i + F_parachute_i
449463

450464
else:
451465
# Aerodynamic forces and moments from the rocket body
452-
mach = air_speed / speed_of_sound
453466
alpha = np.arccos(np.dot(v_relative_wind_b / air_speed, [1, 0, 0]))
454467
cop = self.aero.COP(mach, abs(alpha))
455468
r_cop_cog_b = (cop - cog) * np.array([-1, 0, 0])
@@ -600,12 +613,10 @@ def fdot(self, time, fn):
600613

601614
def run(self, max_time=1000, debug=False, to_json=False):
602615
"""Runs the rocket trajectory simulation. Uses the SciPy DOP853 O(h^8) integrator.
603-
604616
Args:
605617
max_time (float, optional): Maximum time to run the simulation for (s). Defaults to 1000.
606618
debug (bool, optional): If True, data will be printed to the console to aid with debugging. Defaults to False.
607619
to_json (str, optional): Directory to export a .json file to, containing the results of the simulation. If False, no .json file will be produced. Defaults to False.
608-
609620
Returns:
610621
pandas.DataFrame: pandas DataFrame containing the fundamental trajectory results. Most information can be derived from this in post processing.
611622
"time" (array): List of times that all the data corresponds to (s).
@@ -736,14 +747,11 @@ def run(self, max_time=1000, debug=False, to_json=False):
736747

737748
def check_phase(self, debug=False):
738749
"""Check what phase of flight the rocket is in, e.g. on the rail, off the rail, or with the parachute open.
739-
740750
Notes:
741751
- Since this only checks after each time step, there may be a very short period where the rocket is orientated as if it is still on the rail, when it shouldn't be.
742752
- For this reason, it may look like the rocket leaves the rail at an altitude greater than the rail length.
743-
744753
Args:
745754
debug (bool, optional): If True, a message is printed when the rocket leaves the rail. Defaults to False.
746-
747755
Returns:
748756
list: List of events that happened in this step, for the data log.
749757
"""
@@ -802,10 +810,8 @@ def check_phase(self, debug=False):
802810

803811
def from_json(directory):
804812
"""Extract trajectory data from a .json file produced by campyros.Rocket.run(), and convert it into a pandas DataFrame.
805-
806813
Args:
807814
directory (str): .json file directory.
808-
809815
Returns:
810816
pandas.DataFrame: pandas DataFrame containing the fundamental trajectory results. Most information can be derived from this in post processing.
811817
"time" (array): List of times that all the data corresponds to (s).

campyros/tests/test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@
170170

171171
class ExampleTest(unittest.TestCase):
172172
def test_time(self):
173-
self.assertAlmostEqual(run_time, run.time.max(), places=0)
173+
self.assertAlmostEqual(run_time // 5, run.time.max() // 5, places=0)
174174

175175
def test_apogee(self):
176176
run_apogee = max(
@@ -197,7 +197,9 @@ def test_rail(self):
197197

198198
def test_parachute(self):
199199
self.assertAlmostEqual(
200-
test_output.time[parachute_ind], run.time[parachute_ind_run], places=0
200+
test_output.time[parachute_ind] // 5,
201+
run.time[parachute_ind_run] // 5,
202+
places=0,
201203
)
202204

203205
def test_stats(self):

0 commit comments

Comments
 (0)