# Monte Carlo Dispersion Analysis using RocketPy¶

This is an advanced use of RocketPy. This notebook wraps RocketPy’s methods to run a Monte Carlo analysis and predict probability distributions of the rocket’s landing point, apogee and other relevant information.

The main output is a map very similar to the one below. This jupyter notebook presents the Monte Carlo analysis performed for the flight of Valetudo, one of the most famous rockets made by Projeto Jupiter. Valetudo has a diameter of $$80$$ mm and height of $$2.4$$ m. It was built for the first time to be launched at Latin American Space Challenge (LASC) 2019. Indeed, Valetudo came to be launched in 2019 on August 10th by 5:56 pm (local time), propelled by a class K motor called ‘Keron’, a solid motor completely designed and built by Projeto Jupiter. The rocket crossed the sky and reached an $$860$$ m apogee, descending safely by the drogue parachute called “Charmander” and landing with an 18.5 m/s terminal velocity.

We hope you enjoy the flight(s) in this notebook just like everyone in LASC19 did it in real-time!

## Clone repository if using Google Colab¶

If you are running this using Binder, or you are running locally with the necessary files, you do not need to run this. On the other hand, if you are running on Google Colab, make sure to run the cell below to clone the repository and download the necessary files.

[ ]:

!git clone https://github.com/giovaniceotto/RocketPy.git
import os
os.chdir('RocketPy/docs/notebooks/dispersion_analysis')


## Install and Load Necessary Libraries¶

[ ]:

!pip install netCDF4
!pip install rocketpyalpha

:

from datetime import datetime
from time import process_time, perf_counter, time
import glob

from rocketpy import Environment, SolidMotor, Rocket, Flight, Function

import numpy as np
from numpy.random import normal, uniform, choice
from IPython.display import display


Next, we import matplotlib to produce awesome looking plots.

:

%config InlineBackend.figure_formats = ['svg']
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
mpl.rcParams['figure.figsize'] = [8, 5]
mpl.rcParams['figure.dpi'] = 120
mpl.rcParams['font.size'] = 14
mpl.rcParams['legend.fontsize'] = 14
mpl.rcParams['figure.titlesize'] = 14


## Defining Analysis Parameters¶

The analysis parameters are a collection of expected values (and their uncertainties, or standard deviation) that completely defines a rocket flight. As an assumption, the parameters which define the flight can behave in 3 different ways: - the parameter is a completely known and has a constant value (i.e. number of fins) - the parameter can assume certain discrete values with uniform distribution (i.e. the member of an ensemble forecast, which might be any integer from 0 to 9) - the parameter is best represented by a normal (gaussian) distribution with a defined expected value and standard deviation

We implement this using a dictionary, where the key is the name of the parameter and the value is either a tuple or a list, depending on the behaviour of the parameter: - if the parameter is know, its value is represented as a list with a single entry (i.e. "number_of_fins: ") - if the parameter can assume certain discrete values with uniform distribution, its values are represented by a list of possible choices (i.e. "member_of_ensemble_forecast: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]") - if the parameter is best represented by a normal (gaussian) distribution, its value is a tuple with the expected value and its standard deviation (i.e. "rocket_mass": (100, 2), where 100 kg is the expected mass, with uncertainty of plus or minus 2 kg)

:

analysis_parameters = {
# Mass Details
"rocketMass": (8.257, 0.001), # Rocket's dry mass (kg) and its uncertainty (standard deviation)

"impulse": (1415.15, 35.3),                         # Motor total impulse (N*s)
"burnOut": (5.274, 1),                              # Motor burn out time (s)
"grainSeparation": (6/1000, 1/1000),                # Motor's grain separation (axial distance between two grains) (m)
"grainDensity": (1707, 50),                         # Motor's grain density (kg/m^3)
"grainInitialHeight": (120/1000, 1/1000),           # Motor's grain height (m)

"inertiaI": (3.675, 0.03675),                       # Rocket's inertia moment perpendicular to its axis (kg*m^2)
"inertiaZ": (0.007, 0.00007),                       # Rocket's inertia moment relative to its axis (kg*m^2)
"distanceRocketNozzle": (-1.024,0.001),             # Distance between rocket's center of dry mass and nozzle exit plane (m) (negative)
"distanceRocketPropellant": (-0.571,0.001),         # Distance between rocket's center of dry mass and and center of propellant mass (m) (negative)
"powerOffDrag": (0.9081/1.05, 0.033),               # Multiplier for rocket's drag curve. Usually has a mean value of 1 and a uncertainty of 5% to 10%
"powerOnDrag": (0.9081/1.05, 0.033),                # Multiplier for rocket's drag curve. Usually has a mean value of 1 and a uncertainty of 5% to 10%
"noseLength": (0.274, 0.001),                       # Rocket's nose cone length (m)
"noseDistanceToCM": (1.134, 0.001),                 # Axial distance between rocket's center of dry mass and nearest point in its nose cone (m)
"finSpan": (0.077, 0.0005),                         # Fin span (m)
"finRootChord": (0.058, 0.0005),                    # Fin root chord (m)
"finTipChord": (0.018, 0.0005),                     # Fin tip chord (m)
"finDistanceToCM": (-0.906, 0.001),                 # Axial distance between rocket's center of dry mass and nearest point in its fin (m)

# Launch and Environment Details - run help(Environment) and help(Flight) for more information
"inclination": (84.7, 1),                           # Launch rail inclination angle relative to the horizontal plane (degrees)
"railLength": (5.7 , 0.0005),                       # Launch rail length (m)
"ensembleMember": list(range(10)),                  # Members of the ensemble forecast to be used

"CdSDrogue": (0.349*1.3, 0.07),                     # Drag coefficient times reference area for the drogue chute (m^2)
"lag_rec": (1 , 0.5),                               # Time delay between parachute ejection signal is detected and parachute is inflated (s)

"lag_se": (0.73, 0.16)                              # Time delay between sensor signal is received and ejection signal is fired (s)
}


## Creating a Flight Settings Generator¶

Now, we create a generator function which will yield all the necessary inputs for a single flight simulation. Each generated input will be randomly generated according to the analysis_parameters dicitionary set up above.

This is just a helper function to make the code clearer.

:

def flight_settings(analysis_parameters, total_number):
i = 0
while i < total_number:
# Generate a flight setting
flight_setting = {}
for parameter_key, parameter_value in analysis_parameters.items():
if type(parameter_value) is tuple:
flight_setting[parameter_key] =  normal(*parameter_value)
else:
flight_setting[parameter_key] =  choice(parameter_value)

# Skip if certain values are negative, which happens due to the normal curve but isnt realistic
if flight_setting['lag_rec'] < 0 or  flight_setting['lag_se'] < 0: continue

# Update counter
i += 1
# Yield a flight setting
yield flight_setting


## Creating an Export Function¶

Monte Carlo analyses usually contain data from thousands or tens of thousands of simulations. They can easily take hours to run. Therefore, it is very important to save our outputs to a file during the analysis. This way, if something happens, we do not lose our progress.

These next functions take care of that. They export the simulation data to three different files: - dispersion_input_file: A file where each line is a json converted dictionary of flight setting inputs to run a single trajectory simulation; - dispersion_output_file: A file where each line is a json converted dictionary containing the main outputs of a single simulation, such as apogee altitute and maximum velocity; - dispersion_error_file: A file to store the inputs of simulations which raised errors. This can help us debug these simulations later on.

:

def export_flight_data(flight_setting, flight_data, exec_time):
# Generate flight results
flight_result = {"outOfRailTime": flight_data.outOfRailTime,
"outOfRailVelocity": flight_data.outOfRailVelocity,
"apogeeTime": flight_data.apogeeTime,
"apogeeAltitude": flight_data.apogee - Env.elevation,
"apogeeX": flight_data.apogeeX,
"apogeeY": flight_data.apogeeY,
"impactTime": flight_data.tFinal,
"impactX": flight_data.xImpact,
"impactY": flight_data.yImpact,
"impactVelocity": flight_data.impactVelocity,
"initialStaticMargin": flight_data.rocket.staticMargin(0),
"outOfRailStaticMargin": flight_data.rocket.staticMargin(TestFlight.outOfRailTime),
"finalStaticMargin": flight_data.rocket.staticMargin(TestFlight.rocket.motor.burnOutTime),
"numberOfEvents": len(flight_data.parachuteEvents),
"executionTime": exec_time}

# Calculate maximum reached velocity
sol = np.array(flight_data.solution)
flight_data.vx = Function(sol[:, [0, 4]], 'Time (s)', 'Vx (m/s)', 'linear', extrapolation="natural")
flight_data.vy = Function(sol[:, [0, 5]], 'Time (s)', 'Vy (m/s)', 'linear', extrapolation="natural")
flight_data.vz = Function(sol[:, [0, 6]], 'Time (s)', 'Vz (m/s)', 'linear', extrapolation="natural")
flight_data.v = (flight_data.vx**2 + flight_data.vy**2 + flight_data.vz**2)**0.5
flight_data.maxVel = np.amax(flight_data.v.source[:, 1])
flight_result['maxVelocity'] = flight_data.maxVel

# Take care of parachute results
if len(flight_data.parachuteEvents) > 0:
flight_result['drogueTriggerTime'] = flight_data.parachuteEvents
flight_result['drogueInflatedTime'] = flight_data.parachuteEvents + flight_data.parachuteEvents.lag
flight_result['drogueInflatedVelocity'] = flight_data.v(flight_data.parachuteEvents + flight_data.parachuteEvents.lag)
else:
flight_result['drogueTriggerTime'] = 0
flight_result['drogueInflatedTime'] = 0
flight_result['drogueInflatedVelocity'] = 0

# Write flight setting and results to file
dispersion_input_file.write(str(flight_setting) + '\n')
dispersion_output_file.write(str(flight_result) + '\n')

def export_flight_error(flight_setting):
dispersion_error_file.write(str(flight_setting) + '\n')


## Simulating Each Flight Setting¶

Finally, we can start running some simulations!

We start by defining the file name we want to use. Then, we specifiy how many simulations we would like to run by setting the number_of_simulations variable.

It is good practice to run something in the order of 100 simulations first, to check for any possible errors in the code. Once we are confident that everything is working well, we increase the number of simulations to something in the range of 5000 to 50000.

We will loop throught all flight settings, creating the environment, rocket and motor classes with the data of the analysis parameters. For the power off and on drag and thrust curve user should have in hands the .csv (or .eng for comercial motor’s thrust curve).

Tip: A better practice is openning the files in “append” mode, this way we can acumulate our simulations. To do this, just change the ‘a’ (write) argument of the open function in the third, fourth and fifth line of code to a (append).

:

# Basic analysis info
filename = 'dispersion_analysis_outputs/valetudo_rocket_v0'
number_of_simulations = 20000

# Create data files for inputs, outputs and error logging
dispersion_error_file = open(str(filename)+'.disp_errors.txt', 'w')
dispersion_input_file = open(str(filename)+'.disp_inputs.txt', 'w')
dispersion_output_file = open(str(filename)+'.disp_outputs.txt', 'w')

# Initialize counter and timer
i = 0

initial_wall_time = time()
initial_cpu_time = process_time()

# Define basic Environment object
Env = Environment(
railLength=5.7,
date=(2019, 8, 10, 21),
latitude=-23.363611,
longitude=-48.011389
)
Env.setElevation(668)
Env.maxExpectedHeight = 1500
Env.setAtmosphericModel(
type='Ensemble',
file='dispersion_analysis_inputs/LASC2019_reanalysis.nc',
dictionary="ECMWF"
)

# Set up parachutes. This rocket, named Valetudo, only has a drogue chute.
def drogueTrigger(p, y):
# Check if rocket is going down, i.e. if it has passed the apogee
vertical_velocity = y
# Return true to activate parachute once the vertical velocity is negative
return True if vertical_velocity < 0 else False

# Iterate over flight settings
out = display('Starting', display_id=True)
for setting in flight_settings(analysis_parameters, number_of_simulations):
start_time = process_time()
i += 1

# Update environment object
Env.selectEnsembleMember(setting['ensembleMember'])
Env.railLength = setting['railLength']

# Create motor
Keron =  SolidMotor(
thrustSource='dispersion_analysis_inputs/thrustCurve.csv',
burnOut=5.274,
reshapeThrustCurve=(setting['burnOut'], setting['impulse']),
grainNumber=6,
grainSeparation=setting['grainSeparation'],
grainDensity=setting['grainDensity'],
grainInitialHeight=setting['grainInitialHeight'],
interpolationMethod='linear'
)

# Create rocket
Valetudo = Rocket(
motor=Keron,
mass=setting['rocketMass'],
inertiaI=setting['inertiaI'],
inertiaZ=setting['inertiaZ'],
distanceRocketNozzle=setting['distanceRocketNozzle'],
distanceRocketPropellant=setting['distanceRocketPropellant'],
powerOffDrag='dispersion_analysis_inputs/Cd_PowerOff.csv',
powerOnDrag='dispersion_analysis_inputs/Cd_PowerOn.csv'
)
Valetudo.setRailButtons([0.224, -0.93], 30)
# Edit rocket drag
Valetudo.powerOffDrag *= setting["powerOffDrag"]
Valetudo.powerOnDrag *= setting["powerOnDrag"]
# Add rocket nose, fins and tail
length=setting['noseLength'],
kind='vonKarman',
distanceToCM=setting['noseDistanceToCM']
)
n=3,
rootChord=setting['finRootChord'],
tipChord=setting['finTipChord'],
span=setting['finSpan'],
distanceToCM=setting['finDistanceToCM']
)
'Drogue',
CdS=setting['CdSDrogue'],
trigger=drogueTrigger,
samplingRate=105,
lag=setting['lag_rec'] + setting['lag_se'],
noise=(0, 8.3, 0.5)
)

# Run trajectory simulation
try:
TestFlight = Flight(
rocket=Valetudo,
environment=Env,
inclination=setting['inclination'],
maxTime=600
)
export_flight_data(setting, TestFlight, process_time() - start_time)
except Exception as E:
print(E)
export_flight_error(setting)

# Register time
out.update(f"Curent iteration: {i:06d} | Average Time per Iteration: {(process_time() - initial_cpu_time)/i:2.6f} s")

# Done

## Print and save total time
final_string = f"Completed {i} iterations successfully. Total CPU time: {process_time() - initial_cpu_time} s. Total wall time: {time() - initial_wall_time} s"
out.update(final_string)
dispersion_input_file.write(final_string + '\n')
dispersion_output_file.write(final_string + '\n')
dispersion_error_file.write(final_string + '\n')

## Close files
dispersion_input_file.close()
dispersion_output_file.close()
dispersion_error_file.close()

'Curent iteration: 019999 | Average Time per Iteration: 0.000289 s'


## Post-processing Monte Carlo Dispersion Results¶

Now that we have finish running thousands of simulations, it is time to process the results and get some nice graphs out of them!

### Importing Dispersion Analysis Saved Data¶

:

filename = 'dispersion_analysis_outputs/valetudo_rocket_v0'

# Initialize variable to store all results
dispersion_general_results = []

dispersion_results = {"outOfRailTime": [],
"outOfRailVelocity": [],
"apogeeTime": [],
"apogeeAltitude": [],
"apogeeX": [],
"apogeeY": [],
"impactTime": [],
"impactX": [],
"impactY": [],
"impactVelocity": [],
"initialStaticMargin": [],
"outOfRailStaticMargin": [],
"finalStaticMargin": [],
"numberOfEvents": [],
"maxVelocity": [],
"drogueTriggerTime": [],
"drogueInflatedTime": [],
"drogueInflatedVelocity": [],
"executionTime": []}

# Get all dispersion results
# Get file
dispersion_output_file = open(str(filename)+'.disp_outputs.txt', 'r+')

# Read each line of the file and convert to dict
for line in dispersion_output_file:
if line != '{': continue
# Eval results and store them
flight_result = eval(line)
dispersion_general_results.append(flight_result)
for parameter_key, parameter_value in flight_result.items():
dispersion_results[parameter_key].append(parameter_value)

# Close data file
dispersion_output_file.close()

# Print number of flights simulated
N = len(dispersion_general_results)
print('Number of simulations: ', N)

Number of simulations:  20000


## Dispersion Results¶

Now, we plot the histogram for every single output. This shows how are outputs behave. Valuable statistical data can be calculated based on them.

### Out of Rail Time¶

:

print(f'Out of Rail Time -         Mean Value: {np.mean(dispersion_results["outOfRailTime"]):0.3f} s')
print(f'Out of Rail Time - Standard Deviation: {np.std(dispersion_results["outOfRailTime"]):0.3f} s')

plt.figure()
plt.hist(dispersion_results["outOfRailTime"], bins=int(N**0.5))
plt.title('Out of Rail Time')
plt.xlabel('Time (s)')
plt.ylabel('Number of Occurences')
plt.show()

# You can also use Plotly instead of Matplotlib if you wish!
# import plotly.express as px
# fig1 = px.histogram(
#     x=dispersion_results["outOfRailTime"],
#     title='Out of Rail Time',
#     nbins=int(N**0.5)
# )
# fig1.update_layout(
#     xaxis_title_text='Time (s)',
#     yaxis_title_text='Number of occurences'
# )

Out of Rail Time -         Mean Value: 1.084 s
Out of Rail Time - Standard Deviation: 0.183 s ### Out of Rail Velocity¶

:

print(f'Out of Rail Velocity -         Mean Value: {np.mean(dispersion_results["outOfRailVelocity"]):0.3f} m/s')
print(f'Out of Rail Velocity - Standard Deviation: {np.std(dispersion_results["outOfRailVelocity"]):0.3f} m/s')

plt.figure()
plt.hist(dispersion_results["outOfRailVelocity"], bins=int(N**0.5))
plt.title('Out of Rail Velocity')
plt.xlabel('Velocity (m/s)')
plt.ylabel('Number of Occurences')
plt.show()

Out of Rail Velocity -         Mean Value: 23.081 m/s
Out of Rail Velocity - Standard Deviation: 3.116 m/s ### Apogee Time¶

:

print(f'Apogee Time -         Mean Value: {np.mean(dispersion_results["apogeeTime"]):0.3f} s')
print(f'Apogee Time - Standard Deviation: {np.std(dispersion_results["apogeeTime"]):0.3f} s')

plt.figure()
plt.hist(dispersion_results["apogeeTime"], bins=int(N**0.5))
plt.title('Apogee Time')
plt.xlabel('Time (s)')
plt.ylabel('Number of Occurences')
plt.show()

Apogee Time -         Mean Value: 14.305 s
Apogee Time - Standard Deviation: 0.344 s ### Apogee Altitude¶

:

print(f'Apogee Altitude -         Mean Value: {np.mean(dispersion_results["apogeeAltitude"]):0.3f} m')
print(f'Apogee Altitude - Standard Deviation: {np.std(dispersion_results["apogeeAltitude"]):0.3f} m')

plt.figure()
plt.hist(dispersion_results["apogeeAltitude"], bins=int(N**0.5))
plt.title('Apogee Altitude')
plt.xlabel('Altitude (m)')
plt.ylabel('Number of Occurences')
plt.show()

# Real measured apogee for Valetudo = 860 m

Apogee Altitude -         Mean Value: 847.247 m
Apogee Altitude - Standard Deviation: 42.020 m ### Apogee X Position¶

:

print(f'Apogee X Position -         Mean Value: {np.mean(dispersion_results["apogeeX"]):0.3f} m')
print(f'Apogee X Position - Standard Deviation: {np.std(dispersion_results["apogeeX"]):0.3f} m')

plt.figure()
plt.hist(dispersion_results["apogeeX"], bins=int(N**0.5))
plt.title('Apogee X Position')
plt.xlabel('Apogee X Position (m)')
plt.ylabel('Number of Occurences')
plt.show()

Apogee X Position -         Mean Value: 108.492 m
Apogee X Position - Standard Deviation: 24.629 m ### Apogee Y Position¶

:

print(f'Apogee Y Position -         Mean Value: {np.mean(dispersion_results["apogeeY"]):0.3f} m')
print(f'Apogee Y Position - Standard Deviation: {np.std(dispersion_results["apogeeY"]):0.3f} m')

plt.figure()
plt.hist(dispersion_results["apogeeY"], bins=int(N**0.5))
plt.title('Apogee Y Position')
plt.xlabel('Apogee Y Position (m)')
plt.ylabel('Number of Occurences')
plt.show()

Apogee Y Position -         Mean Value: 107.614 m
Apogee Y Position - Standard Deviation: 19.732 m ### Impact Time¶

:

print(f'Impact Time -         Mean Value: {np.mean(dispersion_results["impactTime"]):0.3f} s')
print(f'Impact Time - Standard Deviation: {np.std(dispersion_results["impactTime"]):0.3f} s')

plt.figure()
plt.hist(dispersion_results["impactTime"], bins=int(N**0.5))
plt.title('Impact Time')
plt.xlabel('Time (s)')
plt.ylabel('Number of Occurences')
plt.show()

Impact Time -         Mean Value: 62.911 s
Impact Time - Standard Deviation: 4.159 s ### Impact X Position¶

:

print(f'Impact X Position -         Mean Value: {np.mean(dispersion_results["impactX"]):0.3f} m')
print(f'Impact X Position - Standard Deviation: {np.std(dispersion_results["impactX"]):0.3f} m')

plt.figure()
plt.hist(dispersion_results["impactX"], bins=int(N**0.5))
plt.title('Impact X Position')
plt.xlabel('Impact X Position (m)')
plt.ylabel('Number of Occurences')
plt.show()

Impact X Position -         Mean Value: 364.105 m
Impact X Position - Standard Deviation: 46.835 m ### Impact Y Position¶

:

print(f'Impact Y Position -         Mean Value: {np.mean(dispersion_results["impactY"]):0.3f} m')
print(f'Impact Y Position - Standard Deviation: {np.std(dispersion_results["impactY"]):0.3f} m')

plt.figure()
plt.hist(dispersion_results["impactY"], bins=int(N**0.5))
plt.title('Impact Y Position')
plt.xlabel('Impact Y Position (m)')
plt.ylabel('Number of Occurences')
plt.show()

Impact Y Position -         Mean Value: 16.109 m
Impact Y Position - Standard Deviation: 36.402 m ### Impact Velocity¶

:

print(f'Impact Velocity -         Mean Value: {np.mean(dispersion_results["impactVelocity"]):0.3f} m/s')
print(f'Impact Velocity - Standard Deviation: {np.std(dispersion_results["impactVelocity"]):0.3f} m/s')

plt.figure()
plt.hist(dispersion_results["impactVelocity"], bins=int(N**0.5))
plt.title('Impact Velocity')
# plt.grid()
plt.xlim(-35,0)
plt.xlabel('Velocity (m/s)')
plt.ylabel('Number of Occurences')
plt.show()

Impact Velocity -         Mean Value: -18.280 m/s
Impact Velocity - Standard Deviation: 1.503 m/s ### Static Margin¶

:

print(f'Initial Static Margin -             Mean Value: {np.mean(dispersion_results["initialStaticMargin"]):0.3f} c')
print(f'Initial Static Margin -     Standard Deviation: {np.std(dispersion_results["initialStaticMargin"]):0.3f} c')

print(f'Out of Rail Static Margin -         Mean Value: {np.mean(dispersion_results["outOfRailStaticMargin"]):0.3f} c')
print(f'Out of Rail Static Margin - Standard Deviation: {np.std(dispersion_results["outOfRailStaticMargin"]):0.3f} c')

print(f'Final Static Margin -               Mean Value: {np.mean(dispersion_results["finalStaticMargin"]):0.3f} c')
print(f'Final Static Margin -       Standard Deviation: {np.std(dispersion_results["finalStaticMargin"]):0.3f} c')

plt.figure()
plt.hist(dispersion_results["initialStaticMargin"], label='Initial', bins=int(N**0.5))
plt.hist(dispersion_results["outOfRailStaticMargin"], label='Out of Rail', bins=int(N**0.5))
plt.hist(dispersion_results["finalStaticMargin"], label='Final', bins=int(N**0.5))
plt.legend()
plt.title('Static Margin')
plt.xlabel('Static Margin (c)')
plt.ylabel('Number of Occurences')
plt.show()

Initial Static Margin -             Mean Value: 1.998 c
Initial Static Margin -     Standard Deviation: 0.326 c
Out of Rail Static Margin -         Mean Value: 2.178 c
Out of Rail Static Margin - Standard Deviation: 0.330 c
Final Static Margin -               Mean Value: 3.026 c
Final Static Margin -       Standard Deviation: 0.347 c ### Maximum Velocity¶

:

print(f'Maximum Velocity -         Mean Value: {np.mean(dispersion_results["maxVelocity"]):0.3f} m/s')
print(f'Maximum Velocity - Standard Deviation: {np.std(dispersion_results["maxVelocity"]):0.3f} m/s')

plt.figure()
plt.hist(dispersion_results["maxVelocity"], bins=int(N**0.5))
plt.title('Maximum Velocity')
plt.xlabel('Velocity (m/s)')
plt.ylabel('Number of Occurences')
plt.show()

Maximum Velocity -         Mean Value: 124.881 m/s
Maximum Velocity - Standard Deviation: 5.655 m/s ### Number of Parachute Events¶

This is usefull to check if the parachute was triggered in every flight.

:

plt.figure()
plt.hist(dispersion_results["numberOfEvents"])
plt.title('Parachute Events')
plt.xlabel('Number of Parachute Events')
plt.ylabel('Number of Occurences')
plt.show() ### Drogue Parachute Trigger Time¶

:

print(f'Drogue Parachute Trigger Time -         Mean Value: {np.mean(dispersion_results["drogueTriggerTime"]):0.3f} s')
print(f'Drogue Parachute Trigger Time - Standard Deviation: {np.std(dispersion_results["drogueTriggerTime"]):0.3f} s')

plt.figure()
plt.hist(dispersion_results["drogueTriggerTime"], bins=int(N**0.5))
plt.title('Drogue Parachute Trigger Time')
plt.xlabel('Time (s)')
plt.ylabel('Number of Occurences')
plt.show()

Drogue Parachute Trigger Time -         Mean Value: 13.338 s
Drogue Parachute Trigger Time - Standard Deviation: 0.347 s ### Drogue Parachute Fully Inflated Time¶

:

print(f'Drogue Parachute Fully Inflated Time -         Mean Value: {np.mean(dispersion_results["drogueInflatedTime"]):0.3f} s')
print(f'Drogue Parachute Fully Inflated Time - Standard Deviation: {np.std(dispersion_results["drogueInflatedTime"]):0.3f} s')

plt.figure()
plt.hist(dispersion_results["drogueInflatedTime"], bins=int(N**0.5))
plt.title('Drogue Parachute Fully Inflated Time')
plt.xlabel('Time (s)')
plt.ylabel('Number of Occurences')
plt.show()

Drogue Parachute Fully Inflated Time -         Mean Value: 15.106 s
Drogue Parachute Fully Inflated Time - Standard Deviation: 0.608 s ### Drogue Parachute Fully Inflated Velocity¶

:

print(f'Drogue Parachute Fully Inflated Velocity -         Mean Value: {np.mean(dispersion_results["drogueInflatedVelocity"]):0.3f} m/s')
print(f'Drogue Parachute Fully Inflated Velocity - Standard Deviation: {np.std(dispersion_results["drogueInflatedVelocity"]):0.3f} m/s')

plt.figure()
plt.hist(dispersion_results["drogueInflatedVelocity"], bins=int(N**0.5))
plt.title('Drogue Parachute Fully Inflated Velocity')
plt.xlabel('Velocity m/s)')
plt.ylabel('Number of Occurences')
plt.show()

Drogue Parachute Fully Inflated Velocity -         Mean Value: 13.936 m/s
Drogue Parachute Fully Inflated Velocity - Standard Deviation: 3.091 m/s ### Error Ellipses¶

:

# Import libraries
from matplotlib.patches import Ellipse

# Import background map

# Retrieve dispersion data por apogee and impact XY position
apogeeX = np.array(dispersion_results['apogeeX'])
apogeeY = np.array(dispersion_results['apogeeY'])
impactX = np.array(dispersion_results['impactX'])
impactY = np.array(dispersion_results['impactY'])

# Define function to calculate eigen values
def eigsorted(cov):
vals, vecs = np.linalg.eigh(cov)
order = vals.argsort()[::-1]
return vals[order], vecs[:,order]

# Create plot figure
plt.figure(num=None, figsize=(8, 6), dpi=150, facecolor='w', edgecolor='k')
ax = plt.subplot(111)

# Calculate error ellipses for impact
impactCov = np.cov(impactX, impactY)
impactVals, impactVecs = eigsorted(impactCov)
impactTheta = np.degrees(np.arctan2(*impactVecs[:,0][::-1]))
impactW, impactH = 2 * np.sqrt(impactVals)

# Draw error ellipses for impact
impact_ellipses = []
for j in [1, 2, 3]:
impactEll = Ellipse(xy=(np.mean(impactX), np.mean(impactY)),
width=impactW*j, height=impactH*j,
angle=impactTheta, color='black')
impactEll.set_facecolor((0, 0, 1, 0.2))
impact_ellipses.append(impactEll)

# Calculate error ellipses for apogee
apogeeCov = np.cov(apogeeX, apogeeY)
apogeeVals, apogeeVecs = eigsorted(apogeeCov)
apogeeTheta = np.degrees(np.arctan2(*apogeeVecs[:,0][::-1]))
apogeeW, apogeeH = 2 * np.sqrt(apogeeVals)

# Draw error ellipses for apogee
for j in [1, 2, 3]:
apogeeEll = Ellipse(xy=(np.mean(apogeeX), np.mean(apogeeY)),
width=apogeeW*j, height=apogeeH*j,
angle=apogeeTheta, color='black')
apogeeEll.set_facecolor((0, 1, 0, 0.2))

# Draw launch point
plt.scatter(0, 0, s=30, marker='*', color='black', label='Launch Point')
# Draw apogee points
plt.scatter(apogeeX, apogeeY, s=5, marker='^', color='green', label='Simulated Apogee')
# Draw impact points
plt.scatter(impactX, impactY, s=5, marker='v', color='blue', label='Simulated Landing Point')
# Draw real landing point
plt.scatter(411.89, -61.07, s=20, marker='X', color='red', label='Measured Landing Point')

plt.legend()

# Add title and labels to plot
ax.set_title('1$\sigma$, 2$\sigma$ and 3$\sigma$ Dispersion Ellipses: Apogee and Lading Points')
ax.set_ylabel('North (m)')
ax.set_xlabel('East (m)')

# Add background image to plot
# You can translate the basemap by changing dx and dy (in meters)
dx = 0
dy = 0
plt.imshow(img,zorder=0, extent=[-1000-dx, 1000-dx, -1000-dy, 1000-dy])
plt.axhline(0, color='black', linewidth=0.5)
plt.axvline(0, color='black', linewidth=0.5)
plt.xlim(-100, 700)
plt.ylim(-300, 300)

# Save plot and show result 