diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
new file mode 100644
index 000000000..e35ef8fd0
--- /dev/null
+++ b/.github/workflows/publish-to-pypi.yml
@@ -0,0 +1,34 @@
+# This workflow will upload a Python Package using Twine when a release is created
+# For more information see: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
+
+name: Publish New Version to PyPI
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: read
+
+jobs:
+ deploy:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v3
+ with:
+ python-version: '3.7'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install build
+ - name: Build package
+ run: python -m build
+ - name: Publish package
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml
index 65fbf269e..8bb5bfc03 100644
--- a/.github/workflows/test_pytest.yaml
+++ b/.github/workflows/test_pytest.yaml
@@ -12,7 +12,7 @@ jobs:
- macos-latest
- windows-latest
python-version:
- - 3.8
+ - 3.7
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
diff --git a/docs/conf.py b/docs/conf.py
index 95f4e0ec8..614f78a0a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -24,7 +24,7 @@
author = "Giovani Hidalgo Ceotto"
# The full version, including alpha/beta/rc tags
-release = "0.12.0"
+release = "0.12.1"
# -- General configuration ---------------------------------------------------
diff --git a/docs/notebooks/deployable_payload_example.ipynb b/docs/notebooks/deployable_payload_example.ipynb
index 50f15525f..8e9e196e8 100644
--- a/docs/notebooks/deployable_payload_example.ipynb
+++ b/docs/notebooks/deployable_payload_example.ipynb
@@ -1,698 +1,1411 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Deployable Payload Flight Simulation Example"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Today we try to demonstrate how to use RocketPy to simulate a flight of a rocket\n",
- "that presents a deployable payload."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To run this notebook, we will need:\n",
- "\n",
- "* RocketPy\n",
- "* netCDF4 (to get weather forecasts)\n",
- "* Data files (we will clone RocketPy's repository for these)\n",
- "\n",
- "Therefore, let's run the following lines of code:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!pip install rocketpy netCDF4\n",
- "!git clone https://github.com/RocketPy-Team/RocketPy.git"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "\n",
- "os.chdir(\"RocketPy/docs/notebooks\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we can start!\n",
- "\n",
- "Here we go through a simplified rocket trajectory simulation to get you started. Let's start by importing the rocketpy module."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {},
- "outputs": [],
- "source": [
- "from rocketpy import Environment, SolidMotor, Rocket, Flight, Function, utilities"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "If you are using a version of Jupyter Notebooks, it is recommended to run the following lines of code to make plots which will be shown later interactive and/or higher quality."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Using Google Colab? Uncomment the following line:\n",
- "%config InlineBackend.figure_formats = ['svg']\n",
- "%matplotlib inline\n",
- "\n",
- "# Using Jupyter Notebook/Lab or VSCode? Uncomment the following line:\n",
- "# %matplotlib widget"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Setting Up a Simulation"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Creating an Environment for Spaceport America"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 33,
- "metadata": {},
- "outputs": [],
- "source": [
- "Env = Environment(\n",
- " railLength=5.2, latitude=32.990254, longitude=-106.974998, elevation=1400\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To get weather data from the GFS forecast, available online, we run the following lines.\n",
- "See [Environment Class Usage](environment_class_usage.ipynb) for more information on how to use the Environment class."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 34,
- "metadata": {},
- "outputs": [],
- "source": [
- "import datetime\n",
- "\n",
- "tomorrow = datetime.date.today() + datetime.timedelta(days=1)\n",
- "\n",
- "Env.setDate((tomorrow.year, tomorrow.month, tomorrow.day, 12)) # Hour given in UTC time\n",
- "\n",
- "Env.setAtmosphericModel(type=\"Forecast\", file=\"GFS\")\n",
- "Env.maxExpectedHeight = 8000"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 35,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Launch Site Details\n",
- "\n",
- "Launch Rail Length: 5.2 m\n",
- "Launch Date: 2022-10-06 12:00:00 UTC\n",
- "Launch Site Latitude: 32.99025°\n",
- "Launch Site Longitude: -106.97500°\n",
- "Reference Datum: SIRGAS2000\n",
- "Launch Site UTM coordinates: 315468.64 W 3651938.65 N\n",
- "Launch Site UTM zone: 13S\n",
- "Launch Site Surface Elevation: 1471.5 m\n",
- "\n",
- "\n",
- "Atmospheric Model Details\n",
- "\n",
- "Atmospheric Model Type: Forecast\n",
- "Forecast Maximum Height: 8.000 km\n",
- "Forecast Time Period: From 2022-10-05 00:00:00 to 2022-10-21 00:00:00 UTC\n",
- "Forecast Hour Interval: 3 hrs\n",
- "Forecast Latitude Range: From -90.0 ° To 90.0 °\n",
- "Forecast Longitude Range: From 0.0 ° To 359.75 °\n",
- "\n",
- "\n",
- "Surface Atmospheric Conditions\n",
- "\n",
- "Surface Wind Speed: 3.53 m/s\n",
- "Surface Wind Direction: 9.63°\n",
- "Surface Wind Heading: 189.63°\n",
- "Surface Pressure: 858.08 hPa\n",
- "Surface Temperature: 287.52 K\n",
- "Surface Air Density: 1.040 kg/m³\n",
- "Surface Speed of Sound: 339.92 m/s\n",
- "\n",
- "\n",
- "Atmospheric Model Plots\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": "\n\n\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "Env.info()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Creating a Motor\n",
- "\n",
- "A solid rocket motor is used in this case. See [Solid Motor Class Usage](solid_motor_class_usage.ipynb) for more information on how to use the Motor class."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 36,
- "metadata": {},
- "outputs": [],
- "source": [
- "Pro75M1670 = SolidMotor(\n",
- " thrustSource=\"../../data/motors/Cesaroni_M1670.eng\",\n",
- " burnOut=3.9,\n",
- " grainNumber=5,\n",
- " grainSeparation=5 / 1000,\n",
- " grainDensity=1815,\n",
- " grainOuterRadius=33 / 1000,\n",
- " grainInitialInnerRadius=15 / 1000,\n",
- " grainInitialHeight=120 / 1000,\n",
- " nozzleRadius=33 / 1000,\n",
- " throatRadius=11 / 1000,\n",
- " interpolationMethod=\"linear\",\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 37,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "Motor Details\n",
- "Total Burning Time: 3.9 s\n",
- "Total Propellant Mass: 2.956 kg\n",
- "Propellant Exhaust Velocity: 2038.745 m/s\n",
- "Average Thrust: 1545.218 N\n",
- "Maximum Thrust: 2200.0 N at 0.15 s after ignition.\n",
- "Total Impulse: 6026.350 Ns\n",
- "\n",
- "Plots\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": "\n\n\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "Pro75M1670.info()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Simulating the First Flight Stage"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's start to simulate our rocket's flight. We will use the Environment and Motor objects we created before.\n",
- "\n",
- "We will assume that the payload is be ejected at apogee, however, this can be modified if needed."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We start by defining the value of each relevant mass, ensuring the are correct before continuing."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 48,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Rocket dry mass: 16.24 kg (with Payload)\n",
- "Propellant Mass: 2.956 kg\n",
- "Payload Mass: 4.5 kg\n",
- "Fully loaded Rocket Mass: 19.2 kg\n"
- ]
- }
- ],
- "source": [
- "# 16.241 is the mass of the rocket including the payload but without the propellant\n",
- "PayloadMass = 4.5 # in kg\n",
- "RocketMass = 16.241 - PayloadMass # in kg\n",
- "\n",
- "print(\"Rocket dry mass: {:.4} kg (with Payload)\".format(RocketMass + PayloadMass))\n",
- "print(\"Propellant Mass: {:.4} kg\".format(Pro75M1670.mass(0)))\n",
- "print(\"Payload Mass: {:.4} kg\".format(PayloadMass))\n",
- "print(\n",
- " \"Fully loaded Rocket Mass: {:.4} kg\".format(\n",
- " RocketMass + Pro75M1670.mass(0) + PayloadMass\n",
- " )\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 49,
- "metadata": {},
- "outputs": [],
- "source": [
- "Rocket1 = Rocket(\n",
- " motor=Pro75M1670,\n",
- " radius=127 / 2000,\n",
- " mass=RocketMass + PayloadMass,\n",
- " inertiaI=6.60,\n",
- " inertiaZ=0.0351,\n",
- " distanceRocketNozzle=-1.255,\n",
- " distanceRocketPropellant=-0.85704,\n",
- " powerOffDrag=\"../../data/calisto/powerOffDragCurve.csv\",\n",
- " powerOnDrag=\"../../data/calisto/powerOnDragCurve.csv\",\n",
- ")\n",
- "\n",
- "Rocket1.setRailButtons([0.2, -0.5])\n",
- "\n",
- "NoseCone_Rocket1 = Rocket1.addNose(\n",
- " length=0.55829, kind=\"vonKarman\", distanceToCM=0.71971\n",
- ")\n",
- "\n",
- "FinSet_Rocket1 = Rocket1.addFins(\n",
- " 4, span=0.100, rootChord=0.120, tipChord=0.040, distanceToCM=-1.04956\n",
- ")\n",
- "\n",
- "Tail_Rocket1 = Rocket1.addTail(\n",
- " topRadius=0.0635, bottomRadius=0.0435, length=0.060, distanceToCM=-1.194656\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 50,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Inertia Details\n",
- "Rocket Dry Mass: 16.241 kg (No Propellant)\n",
- "Rocket Total Mass: 19.196911961392022 kg (With Propellant)\n",
- "\n",
- "Geometrical Parameters\n",
- "Rocket Radius: 0.0635 m\n",
- "\n",
- "Aerodynamics Stability\n",
- "Initial Static Margin: 2.051 c\n",
- "Final Static Margin: 3.090 c\n",
- "\n",
- "Aerodynamics Plots\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": "\n\n\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "Rocket1.info()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 51,
- "metadata": {},
- "outputs": [],
- "source": [
- "RocketFlight1 = Flight(\n",
- " rocket=Rocket1, environment=Env, inclination=85, heading=25, terminateOnApogee=True\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Start the Second Flight Stage"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we will simulate the second flight stage, which is the landing phase of our Rocket.\n",
- "Here we will consider that the payload was ejected at the apogee of the first stage.\n",
- "Therefore we should be careful with the value of its mass."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 52,
- "metadata": {},
- "outputs": [],
- "source": [
- "Rocket2 = Rocket(\n",
- " motor=Pro75M1670, # This motor will not be used\n",
- " radius=127 / 2000,\n",
- " mass=RocketMass,\n",
- " inertiaI=6.60,\n",
- " inertiaZ=0.0351,\n",
- " distanceRocketNozzle=-1.255,\n",
- " distanceRocketPropellant=-0.85704,\n",
- " powerOffDrag=1,\n",
- " powerOnDrag=1,\n",
- ")\n",
- "\n",
- "\n",
- "def drogueTrigger(p, y):\n",
- " # p = pressure\n",
- " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
- " # activate drogue when vz < 0 m/s.\n",
- " return True if y[5] < 0 else False\n",
- "\n",
- "\n",
- "def mainTrigger(p, y):\n",
- " # p = pressure\n",
- " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
- " # activate main when vz < 0 m/s and z < 800 + 1400 m (+1400 due to surface elevation).\n",
- " return True if y[5] < 0 and y[2] < 800 + 1400 else False\n",
- "\n",
- "\n",
- "# Define Parachutes for the rocket\n",
- "Main_Rocket2 = Rocket2.addParachute(\n",
- " \"Main\",\n",
- " CdS=7.2,\n",
- " trigger=mainTrigger,\n",
- " samplingRate=105,\n",
- " lag=1.5,\n",
- " noise=(0, 8.3, 0.5),\n",
- ")\n",
- "\n",
- "Drogue_Rocket2 = Rocket2.addParachute(\n",
- " \"Drogue\",\n",
- " CdS=0.72,\n",
- " trigger=drogueTrigger,\n",
- " samplingRate=105,\n",
- " lag=1.5,\n",
- " noise=(0, 8.3, 0.5),\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 53,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Inertia Details\n",
- "Rocket Dry Mass: 11.741 kg (No Propellant)\n",
- "Rocket Total Mass: 14.696911961392022 kg (With Propellant)\n",
- "\n",
- "Geometrical Parameters\n",
- "Rocket Radius: 0.0635 m\n",
- "\n",
- "Aerodynamics Stability\n",
- "Initial Static Margin: -1.357 c\n",
- "Final Static Margin: -0.000 c\n",
- "\n",
- "Main Parachute\n",
- "CdS Coefficient: 7.2 m2\n",
- "\n",
- "Drogue Parachute\n",
- "CdS Coefficient: 0.72 m2\n",
- "\n",
- "Aerodynamics Plots\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": "\n\n\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "Rocket2.info()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The magic line `initialSolution=RocketFlight1` will make the simulation start from the end of the first stage."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 54,
- "metadata": {},
- "outputs": [],
- "source": [
- "RocketFlight2 = Flight(\n",
- " rocket=Rocket2,\n",
- " environment=Env,\n",
- " inclination=0,\n",
- " heading=0,\n",
- " maxTime=600,\n",
- " initialSolution=RocketFlight1,\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Simulating the 3 Flight Stage - Payload Flight"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Here we will simulate the payload flight, which is the third flight stage of our Rocket.\n",
- "The Payload will be ejected at the apogee of the first stage.\n",
- "Here, it will be modelled as a \"dummy\" rocket, which does not have any aerodynamic surfaces to stabilize it, neither a motor which ignites.\n",
- "It does, however, have parachutes."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 55,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Define the \"Payload Rocket\"\n",
- "\n",
- "PayloadRocket = Rocket(\n",
- " motor=Pro75M1670, # This motor will not be used\n",
- " radius=127 / 2000,\n",
- " mass=PayloadMass,\n",
- " inertiaI=6.60,\n",
- " inertiaZ=0.0351,\n",
- " distanceRocketNozzle=-1.255,\n",
- " distanceRocketPropellant=-0.85704,\n",
- " powerOffDrag=0.5,\n",
- " powerOnDrag=0.5,\n",
- ")\n",
- "\n",
- "\n",
- "def drogueTrigger(p, y):\n",
- " # p = pressure\n",
- " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
- " # activate drogue when vz < 0 m/s.\n",
- " return True if y[5] < 0 else False\n",
- "\n",
- "\n",
- "def mainTrigger(p, y):\n",
- " # p = pressure\n",
- " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
- " # activate main when vz < 0 m/s and z < 800 + 1400 m (+1400 due to surface elevation).\n",
- " return True if y[5] < 0 and y[2] < 800 + 1400 else False\n",
- "\n",
- "\n",
- "PayloadDrogue = PayloadRocket.addParachute(\n",
- " \"Drogue\",\n",
- " CdS=0.35,\n",
- " trigger=drogueTrigger,\n",
- " samplingRate=105,\n",
- " lag=1.5,\n",
- " noise=(0, 8.3, 0.5),\n",
- ")\n",
- "\n",
- "PayloadMain = PayloadRocket.addParachute(\n",
- " \"Main\",\n",
- " CdS=4.0,\n",
- " trigger=mainTrigger,\n",
- " samplingRate=105,\n",
- " lag=1.5,\n",
- " noise=(0, 8.3, 0.5),\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The magic line `initialSolution=RocketFlight1` will make the simulation start from the end of the first stage."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 56,
- "metadata": {},
- "outputs": [],
- "source": [
- "PayloadFlight = Flight(\n",
- " rocket=PayloadRocket,\n",
- " environment=Env,\n",
- " inclination=0,\n",
- " heading=0,\n",
- " maxTime=600,\n",
- " initialSolution=RocketFlight1,\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Plotting Everything together"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We will invoke a method from RocketPy's utilities class in order to visualize \n",
- "the trajectory."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/svg+xml": "\n\n\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "utilities.compareFlightTrajectories(\n",
- " flight_list=[RocketFlight1, RocketFlight2, PayloadFlight],\n",
- " names=[\"Rocket - 1st Stage\", \"Rocket - 2nd Stage\", \"Payload Flight\"],\n",
- ")"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3.10.6 64-bit",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.6"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "26de051ba29f2982a8de78e945f0abaf191376122a1563185a90213a26c5da77"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Deployable Payload Flight Simulation Example"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Today we try to demonstrate how to use RocketPy to simulate a flight of a rocket\n",
+ "that presents a deployable payload."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To run this notebook, we will need:\n",
+ "\n",
+ "* RocketPy\n",
+ "* netCDF4 (to get weather forecasts)\n",
+ "* Data files (we will clone RocketPy's repository for these)\n",
+ "\n",
+ "Therefore, let's run the following lines of code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install rocketpy netCDF4\n",
+ "!git clone https://github.com/RocketPy-Team/RocketPy.git"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "os.chdir(\"RocketPy/docs/notebooks\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we can start!\n",
+ "\n",
+ "Here we go through a simplified rocket trajectory simulation to get you started. Let's start by importing the rocketpy module."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from rocketpy import Environment, SolidMotor, Rocket, Flight, Function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "If you are using a version of Jupyter Notebooks, it is recommended to run the following lines of code to make plots which will be shown later interactive and/or higher quality."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Using Google Colab? Uncomment the following line:\n",
+ "%config InlineBackend.figure_formats = ['svg']\n",
+ "%matplotlib inline\n",
+ "\n",
+ "# Using Jupyter Notebook/Lab or VSCode? Uncomment the following line:\n",
+ "# %matplotlib widget"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setting Up a Simulation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Creating an Environment for Spaceport America"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Env = Environment(\n",
+ " railLength=5.2, latitude=32.990254, longitude=-106.974998, elevation=1400\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To get weather data from the GFS forecast, available online, we run the following lines.\n",
+ "See [Environment Class Usage](environment_class_usage.ipynb) for more information on how to use the Environment class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import datetime\n",
+ "\n",
+ "tomorrow = datetime.date.today() + datetime.timedelta(days=1)\n",
+ "\n",
+ "Env.setDate((tomorrow.year, tomorrow.month, tomorrow.day, 12)) # Hour given in UTC time\n",
+ "\n",
+ "Env.setAtmosphericModel(type=\"Forecast\", file=\"GFS\")\n",
+ "Env.maxExpectedHeight = 8000"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Launch Site Details\n",
+ "\n",
+ "Launch Rail Length: 5.2 m\n",
+ "Launch Date: 2022-10-06 12:00:00 UTC\n",
+ "Launch Site Latitude: 32.99025°\n",
+ "Launch Site Longitude: -106.97500°\n",
+ "Reference Datum: SIRGAS2000\n",
+ "Launch Site UTM coordinates: 315468.64 W 3651938.65 N\n",
+ "Launch Site UTM zone: 13S\n",
+ "Launch Site Surface Elevation: 1471.5 m\n",
+ "\n",
+ "\n",
+ "Atmospheric Model Details\n",
+ "\n",
+ "Atmospheric Model Type: Forecast\n",
+ "Forecast Maximum Height: 8.000 km\n",
+ "Forecast Time Period: From 2022-10-05 06:00:00 to 2022-10-21 06:00:00 UTC\n",
+ "Forecast Hour Interval: 3 hrs\n",
+ "Forecast Latitude Range: From -90.0 ° To 90.0 °\n",
+ "Forecast Longitude Range: From 0.0 ° To 359.75 °\n",
+ "\n",
+ "\n",
+ "Surface Atmospheric Conditions\n",
+ "\n",
+ "Surface Wind Speed: 4.03 m/s\n",
+ "Surface Wind Direction: 61.94°\n",
+ "Surface Wind Heading: 182.37°\n",
+ "Surface Pressure: 858.27 hPa\n",
+ "Surface Temperature: 287.78 K\n",
+ "Surface Air Density: 1.039 kg/m³\n",
+ "Surface Speed of Sound: 340.08 m/s\n",
+ "\n",
+ "\n",
+ "Atmospheric Model Plots\n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Env.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Creating a Motor\n",
+ "\n",
+ "A solid rocket motor is used in this case. See [Solid Motor Class Usage](solid_motor_class_usage.ipynb) for more information on how to use the Motor class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Pro75M1670 = SolidMotor(\n",
+ " thrustSource=\"../../data/motors/Cesaroni_M1670.eng\",\n",
+ " burnOut=3.9,\n",
+ " grainNumber=5,\n",
+ " grainSeparation=5 / 1000,\n",
+ " grainDensity=1815,\n",
+ " grainOuterRadius=33 / 1000,\n",
+ " grainInitialInnerRadius=15 / 1000,\n",
+ " grainInitialHeight=120 / 1000,\n",
+ " nozzleRadius=33 / 1000,\n",
+ " throatRadius=11 / 1000,\n",
+ " interpolationMethod=\"linear\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Motor Details\n",
+ "Total Burning Time: 3.9 s\n",
+ "Total Propellant Mass: 2.956 kg\n",
+ "Propellant Exhaust Velocity: 2038.745 m/s\n",
+ "Average Thrust: 1545.218 N\n",
+ "Maximum Thrust: 2200.0 N at 0.15 s after ignition.\n",
+ "Total Impulse: 6026.350 Ns\n",
+ "\n",
+ "Plots\n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Pro75M1670.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Simulating the First Flight Stage"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's start to simulate our rocket's flight. We will use the Environment and Motor objects we created before.\n",
+ "\n",
+ "We will assume that the payload is be ejected at apogee, however, this can be modified if needed."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We start by defining the value of each relevant mass, ensuring the are correct before continuing."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Rocket dry mass: 16.24 kg (with Payload)\n",
+ "Propellant Mass: 2.956 kg\n",
+ "Payload Mass: 4.5 kg\n",
+ "Fully loaded Rocket Mass: 19.2 kg\n"
+ ]
+ }
+ ],
+ "source": [
+ "# 16.241 is the mass of the rocket including the payload but without the propellant\n",
+ "PayloadMass = 4.5 # in kg\n",
+ "RocketMass = 16.241 - PayloadMass # in kg\n",
+ "\n",
+ "print(\"Rocket dry mass: {:.4} kg (with Payload)\".format(RocketMass + PayloadMass))\n",
+ "print(\"Propellant Mass: {:.4} kg\".format(Pro75M1670.mass(0)))\n",
+ "print(\"Payload Mass: {:.4} kg\".format(PayloadMass))\n",
+ "print(\n",
+ " \"Fully loaded Rocket Mass: {:.4} kg\".format(\n",
+ " RocketMass + Pro75M1670.mass(0) + PayloadMass\n",
+ " )\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Rocket1 = Rocket(\n",
+ " motor=Pro75M1670,\n",
+ " radius=127 / 2000,\n",
+ " mass=RocketMass + PayloadMass,\n",
+ " inertiaI=6.60,\n",
+ " inertiaZ=0.0351,\n",
+ " distanceRocketNozzle=-1.255,\n",
+ " distanceRocketPropellant=-0.85704,\n",
+ " powerOffDrag=\"../../data/calisto/powerOffDragCurve.csv\",\n",
+ " powerOnDrag=\"../../data/calisto/powerOnDragCurve.csv\",\n",
+ ")\n",
+ "\n",
+ "Rocket1.setRailButtons([0.2, -0.5])\n",
+ "\n",
+ "NoseCone_Rocket1 = Rocket1.addNose(\n",
+ " length=0.55829, kind=\"vonKarman\", distanceToCM=0.71971\n",
+ ")\n",
+ "\n",
+ "FinSet_Rocket1 = Rocket1.addFins(\n",
+ " 4, span=0.100, rootChord=0.120, tipChord=0.040, distanceToCM=-1.04956\n",
+ ")\n",
+ "\n",
+ "Tail_Rocket1 = Rocket1.addTail(\n",
+ " topRadius=0.0635, bottomRadius=0.0435, length=0.060, distanceToCM=-1.194656\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Inertia Details\n",
+ "Rocket Dry Mass: 16.241 kg (No Propellant)\n",
+ "Rocket Total Mass: 19.196911961392022 kg (With Propellant)\n",
+ "\n",
+ "Geometrical Parameters\n",
+ "Rocket Radius: 0.0635 m\n",
+ "\n",
+ "Aerodynamics Stability\n",
+ "Initial Static Margin: 2.051 c\n",
+ "Final Static Margin: 3.090 c\n",
+ "\n",
+ "Aerodynamics Plots\n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Rocket1.info()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "RocketFlight1 = Flight(\n",
+ " rocket=Rocket1, environment=Env, inclination=85, heading=25, terminateOnApogee=True\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Start the Second Flight Stage"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we will simulate the second flight stage, which is the landing phase of our Rocket.\n",
+ "Here we will consider that the payload was ejected at the apogee of the first stage.\n",
+ "Therefore we should be careful with the value of its mass."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Rocket2 = Rocket(\n",
+ " motor=Pro75M1670, # This motor will not be used\n",
+ " radius=127 / 2000,\n",
+ " mass=RocketMass,\n",
+ " inertiaI=6.60,\n",
+ " inertiaZ=0.0351,\n",
+ " distanceRocketNozzle=-1.255,\n",
+ " distanceRocketPropellant=-0.85704,\n",
+ " powerOffDrag=1,\n",
+ " powerOnDrag=1,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "def drogueTrigger(p, y):\n",
+ " # p = pressure\n",
+ " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
+ " # activate drogue when vz < 0 m/s.\n",
+ " return True if y[5] < 0 else False\n",
+ "\n",
+ "\n",
+ "def mainTrigger(p, y):\n",
+ " # p = pressure\n",
+ " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
+ " # activate main when vz < 0 m/s and z < 800 + 1400 m (+1400 due to surface elevation).\n",
+ " return True if y[5] < 0 and y[2] < 800 + 1400 else False\n",
+ "\n",
+ "\n",
+ "# Define Parachutes for the rocket\n",
+ "Main_Rocket2 = Rocket2.addParachute(\n",
+ " \"Main\",\n",
+ " CdS=7.2,\n",
+ " trigger=mainTrigger,\n",
+ " samplingRate=105,\n",
+ " lag=1.5,\n",
+ " noise=(0, 8.3, 0.5),\n",
+ ")\n",
+ "\n",
+ "Drogue_Rocket2 = Rocket2.addParachute(\n",
+ " \"Drogue\",\n",
+ " CdS=0.72,\n",
+ " trigger=drogueTrigger,\n",
+ " samplingRate=105,\n",
+ " lag=1.5,\n",
+ " noise=(0, 8.3, 0.5),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Inertia Details\n",
+ "Rocket Dry Mass: 11.741 kg (No Propellant)\n",
+ "Rocket Total Mass: 14.696911961392022 kg (With Propellant)\n",
+ "\n",
+ "Geometrical Parameters\n",
+ "Rocket Radius: 0.0635 m\n",
+ "\n",
+ "Aerodynamics Stability\n",
+ "Initial Static Margin: -1.357 c\n",
+ "Final Static Margin: -0.000 c\n",
+ "\n",
+ "Main Parachute\n",
+ "CdS Coefficient: 7.2 m2\n",
+ "\n",
+ "Drogue Parachute\n",
+ "CdS Coefficient: 0.72 m2\n",
+ "\n",
+ "Aerodynamics Plots\n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Rocket2.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The magic line `initialSolution=RocketFlight1` will make the simulation start from the end of the first stage."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "RocketFlight2 = Flight(\n",
+ " rocket=Rocket2,\n",
+ " environment=Env,\n",
+ " inclination=0,\n",
+ " heading=0,\n",
+ " maxTime=600,\n",
+ " initialSolution=RocketFlight1,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Simulating the 3 Flight Stage - Payload Flight"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Here we will simulate the payload flight, which is the third flight stage of our Rocket.\n",
+ "The Payload will be ejected at the apogee of the first stage.\n",
+ "Here, it will be modelled as a \"dummy\" rocket, which does not have any aerodynamic surfaces to stabilize it, neither a motor which ignites.\n",
+ "It does, however, have parachutes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define the \"Payload Rocket\"\n",
+ "\n",
+ "PayloadRocket = Rocket(\n",
+ " motor=Pro75M1670, # This motor will not be used\n",
+ " radius=127 / 2000,\n",
+ " mass=PayloadMass,\n",
+ " inertiaI=6.60,\n",
+ " inertiaZ=0.0351,\n",
+ " distanceRocketNozzle=-1.255,\n",
+ " distanceRocketPropellant=-0.85704,\n",
+ " powerOffDrag=0.5,\n",
+ " powerOnDrag=0.5,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "def drogueTrigger(p, y):\n",
+ " # p = pressure\n",
+ " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
+ " # activate drogue when vz < 0 m/s.\n",
+ " return True if y[5] < 0 else False\n",
+ "\n",
+ "\n",
+ "def mainTrigger(p, y):\n",
+ " # p = pressure\n",
+ " # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]\n",
+ " # activate main when vz < 0 m/s and z < 800 + 1400 m (+1400 due to surface elevation).\n",
+ " return True if y[5] < 0 and y[2] < 800 + 1400 else False\n",
+ "\n",
+ "\n",
+ "PayloadDrogue = PayloadRocket.addParachute(\n",
+ " \"Drogue\",\n",
+ " CdS=0.35,\n",
+ " trigger=drogueTrigger,\n",
+ " samplingRate=105,\n",
+ " lag=1.5,\n",
+ " noise=(0, 8.3, 0.5),\n",
+ ")\n",
+ "\n",
+ "PayloadMain = PayloadRocket.addParachute(\n",
+ " \"Main\",\n",
+ " CdS=4.0,\n",
+ " trigger=mainTrigger,\n",
+ " samplingRate=105,\n",
+ " lag=1.5,\n",
+ " noise=(0, 8.3, 0.5),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The magic line `initialSolution=RocketFlight1` will make the simulation start from the end of the first stage."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "PayloadFlight = Flight(\n",
+ " rocket=PayloadRocket,\n",
+ " environment=Env,\n",
+ " inclination=0,\n",
+ " heading=0,\n",
+ " maxTime=600,\n",
+ " initialSolution=RocketFlight1,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Plotting Everything together"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will invoke a method from RocketPy's flight_plots module in order to visualize \n",
+ "the trajectory."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from rocketpy.plots import flight_plots"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "By initializing the flight_plots class, we can plot the trajectory ad other\n",
+ "important information about the flights. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flight_plotter = flight_plots.flight_plots(\n",
+ " trajectory_list=[RocketFlight1, RocketFlight2, PayloadFlight],\n",
+ " names_list=[\"Rocket - 1st Stage\", \"Rocket - 2nd Stage\", \"Payload\"],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Numerical information are available by calling `.info()` method."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Initial Conditions for Flight: Rocket - 1st Stage\n",
+ "Position - x: 0.00 m | y: 0.00 m | z: 1471.47 m\n",
+ "Velocity - Vx: 0.00 m/s | Vy: 0.00 m/s | Vz: 0.00 m/s\n",
+ "Attitude - e0: 0.975 | e1: -0.043 | e2: 0.009 | e3: -0.216\n",
+ "Euler Angles - Spin φ : 0.00° | Nutation θ: -5.00° | Precession ψ: -25.00°\n",
+ "Angular Velocity - ω1: 0.00 rad/s | ω2: 0.00 rad/s| ω3: 0.00 rad/s\n",
+ "\n",
+ "Initial Conditions for Flight: Rocket - 2nd Stage\n",
+ "Position - x: 39485.37 m | y: 133869.35 m | z: 4345914.80 m\n",
+ "Velocity - Vx: -191.43 m/s | Vy: -723.16 m/s | Vz: -47595.99 m/s\n",
+ "Attitude - e0: -67496.659 | e1: 63232.013 | e2: -4205.673 | e3: 15122.327\n",
+ "Euler Angles - Spin φ : -8.82° | Nutation θ: -84.72° | Precession ψ: -16.43°\n",
+ "Angular Velocity - ω1: -0.35 rad/s | ω2: -0.05 rad/s| ω3: 0.00 rad/s\n",
+ "\n",
+ "Initial Conditions for Flight: Payload\n",
+ "Position - x: 51501.13 m | y: 174607.13 m | z: 4349500.80 m\n",
+ "Velocity - Vx: -176.89 m/s | Vy: -685.68 m/s | Vz: -62960.94 m/s\n",
+ "Attitude - e0: -67498.155 | e1: 63230.993 | e2: -4205.605 | e3: 15122.661\n",
+ "Euler Angles - Spin φ : -8.82° | Nutation θ: -84.72° | Precession ψ: -16.43°\n",
+ "Angular Velocity - ω1: -0.35 rad/s | ω2: -0.05 rad/s| ω3: 0.00 rad/s\n",
+ "\n",
+ "Surface Wind Conditions of Flight: Rocket - 1st Stage\n",
+ "Frontal Surface Wind Speed: -3.72 m/s\n",
+ "Lateral Surface Wind Speed: -1.54 m/s\n",
+ "\n",
+ "Surface Wind Conditions of Flight: Rocket - 2nd Stage\n",
+ "Frontal Surface Wind Speed: -4.02 m/s\n",
+ "Lateral Surface Wind Speed: 0.17 m/s\n",
+ "\n",
+ "Surface Wind Conditions of Flight: Payload\n",
+ "Frontal Surface Wind Speed: -4.02 m/s\n",
+ "Lateral Surface Wind Speed: 0.17 m/s\n",
+ "\n",
+ "Launch Rail Orientation of Flight: Rocket - 1st Stage\n",
+ "Launch Rail Inclination: 85.00°\n",
+ "Launch Rail Heading: 25.00°\n",
+ "\n",
+ "Launch Rail Orientation of Flight: Rocket - 2nd Stage\n",
+ "Launch Rail Inclination: 0.00°\n",
+ "Launch Rail Heading: 0.00°\n",
+ "\n",
+ "Launch Rail Orientation of Flight: Payload\n",
+ "Launch Rail Inclination: 0.00°\n",
+ "Launch Rail Heading: 0.00°\n",
+ "\n",
+ "Rail Departure State of Flight: Rocket - 1st Stage\n",
+ "Rail Departure Time: 0.415 s\n",
+ "Rail Departure Velocity: 30.476 m/s\n",
+ "Rail Departure Static Margin: 2.148 c\n",
+ "Rail Departure Angle of Attack: 7.440°\n",
+ "Rail Departure Thrust-Weight Ratio: 10.308\n",
+ "Rail Departure Reynolds Number: 2.292e+05\n",
+ "\n",
+ "Rail Departure State of Flight: Rocket - 2nd Stage\n",
+ "Rail Departure Time: 25.834 s\n",
+ "Rail Departure Velocity: 0.000 m/s\n",
+ "Rail Departure Static Margin: -0.000 c\n",
+ "Rail Departure Angle of Attack: 5.277°\n",
+ "Rail Departure Thrust-Weight Ratio: 0.000\n",
+ "Rail Departure Reynolds Number: 1.578e+05\n",
+ "\n",
+ "Rail Departure State of Flight: Payload\n",
+ "Rail Departure Time: 25.834 s\n",
+ "Rail Departure Velocity: 0.000 m/s\n",
+ "Rail Departure Static Margin: -0.000 c\n",
+ "Rail Departure Angle of Attack: 5.277°\n",
+ "Rail Departure Thrust-Weight Ratio: 0.000\n",
+ "Rail Departure Reynolds Number: 1.578e+05\n",
+ "\n",
+ "BurnOut State of Flight: Rocket - 1st Stage\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 654.629 m (AGL)\n",
+ "Rocket velocity at burnOut: 280.268 m/s\n",
+ "Freestream velocity at burnOut: 280.291 m/s\n",
+ "Mach Number at burnOut: 0.831\n",
+ "Kinetic energy at burnOut: 6.379e+05 J\n",
+ "\n",
+ "BurnOut State of Flight: Rocket - 2nd Stage\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 2660233.688 m (AGL)\n",
+ "Rocket velocity at burnOut: 30.180 m/s\n",
+ "Freestream velocity at burnOut: 28.069 m/s\n",
+ "Mach Number at burnOut: 0.086\n",
+ "Kinetic energy at burnOut: 5.347e+03 J\n",
+ "\n",
+ "BurnOut State of Flight: Payload\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 2662428.447 m (AGL)\n",
+ "Rocket velocity at burnOut: 30.180 m/s\n",
+ "Freestream velocity at burnOut: 28.069 m/s\n",
+ "Mach Number at burnOut: 0.086\n",
+ "Kinetic energy at burnOut: 2.050e+03 J\n",
+ "\n",
+ "Apogee State of Flight: Rocket - 1st Stage\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "\n",
+ "Apogee State of Flight: Rocket - 2nd Stage\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "\n",
+ "Apogee State of Flight: Payload\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "\n",
+ "Parachute Events of Flight: Rocket - 1st Stage\n",
+ "No Parachute Events Were Triggered.\n",
+ "\n",
+ "Parachute Events of Flight: Rocket - 2nd Stage\n",
+ "Drogue Ejection Triggered at: 25.838 s\n",
+ "Drogue Parachute Inflated at: 27.338 s\n",
+ "Drogue Parachute Inflated with Freestream Speed of: 31.228 m/s\n",
+ "Drogue Parachute Inflated at Height of: 3285.072 m (AGL)\n",
+ "Main Ejection Triggered at: 160.562 s\n",
+ "Main Parachute Inflated at: 162.062 s\n",
+ "Main Parachute Inflated with Freestream Speed of: 18.171 m/s\n",
+ "Main Parachute Inflated at Height of: 701.231 m (AGL)\n",
+ "\n",
+ "Parachute Events of Flight: Payload\n",
+ "Drogue Ejection Triggered at: 25.838 s\n",
+ "Drogue Parachute Inflated at: 27.338 s\n",
+ "Drogue Parachute Inflated with Freestream Speed of: 31.083 m/s\n",
+ "Drogue Parachute Inflated at Height of: 3285.082 m (AGL)\n",
+ "Main Ejection Triggered at: 177.676 s\n",
+ "Main Parachute Inflated at: 179.176 s\n",
+ "Main Parachute Inflated with Freestream Speed of: 16.153 m/s\n",
+ "Main Parachute Inflated at Height of: 704.169 m (AGL)\n",
+ "\n",
+ "Impact Conditions of Flight: Rocket - 1st Stage\n",
+ "X Impact: 0.000 m\n",
+ "Y Impact: 0.000 m\n",
+ "Time of Impact: 25.834 s\n",
+ "Velocity at Impact: 0.000 m/s\n",
+ "\n",
+ "Impact Conditions of Flight: Rocket - 2nd Stage\n",
+ "X Impact: 263.439 m\n",
+ "Y Impact: 902.739 m\n",
+ "Time of Impact: 285.440 s\n",
+ "Velocity at Impact: -5.548 m/s\n",
+ "\n",
+ "Impact Conditions of Flight: Payload\n",
+ "X Impact: 270.859 m\n",
+ "Y Impact: 929.654 m\n",
+ "Time of Impact: 327.907 s\n",
+ "Velocity at Impact: -4.608 m/s\n",
+ "\n",
+ "Maximum Values of Flight: Rocket - 1st Stage\n",
+ "Maximum Speed: 286.303 m/s at 3.38 s\n",
+ "Maximum Mach Number: 0.847 Mach at 3.39 s\n",
+ "Maximum Reynolds Number: 2.034e+06 at 3.33 s\n",
+ "Maximum Dynamic Pressure: 4.062e+04 Pa at 3.35 s\n",
+ "Maximum Acceleration: 105.099 m/s² at 0.15 s\n",
+ "Maximum Gs: 10.717 g at 0.15 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.383 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.383 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.383 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.383 N\n",
+ "\n",
+ "Maximum Values of Flight: Rocket - 2nd Stage\n",
+ "Maximum Speed: 33.104 m/s at 27.34 s\n",
+ "Maximum Mach Number: 0.095 Mach at 27.34 s\n",
+ "Maximum Reynolds Number: 1.757e+05 at 27.34 s\n",
+ "Maximum Dynamic Pressure: 3.644e+02 Pa at 27.34 s\n",
+ "Maximum Acceleration: 40.250 m/s² at 162.06 s\n",
+ "Maximum Gs: 4.104 g at 162.06 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.000 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.000 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.000 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.000 N\n",
+ "\n",
+ "Maximum Values of Flight: Payload\n",
+ "Maximum Speed: 32.959 m/s at 27.34 s\n",
+ "Maximum Mach Number: 0.095 Mach at 27.34 s\n",
+ "Maximum Reynolds Number: 1.749e+05 at 27.34 s\n",
+ "Maximum Dynamic Pressure: 3.611e+02 Pa at 27.34 s\n",
+ "Maximum Acceleration: 25.033 m/s² at 179.18 s\n",
+ "Maximum Gs: 2.553 g at 179.18 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.000 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.000 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.000 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.000 N\n",
+ "\n",
+ "Numerical Integration Settings of Flight: Rocket - 1st Stage\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: True\n",
+ "Number of Time Steps Used: 505\n",
+ "Number of Derivative Functions Evaluation: 1518\n",
+ "Average Function Evaluations per Time Step: 3.005941\n",
+ "\n",
+ "Numerical Integration Settings of Flight: Rocket - 2nd Stage\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: False\n",
+ "Number of Time Steps Used: 230\n",
+ "Number of Derivative Functions Evaluation: 743\n",
+ "Average Function Evaluations per Time Step: 3.230435\n",
+ "\n",
+ "Numerical Integration Settings of Flight: Payload\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: False\n",
+ "Number of Time Steps Used: 219\n",
+ "Number of Derivative Functions Evaluation: 639\n",
+ "Average Function Evaluations per Time Step: 2.917808\n"
+ ]
+ }
+ ],
+ "source": [
+ "flight_plotter.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We will mode straight forward and plot all the available charts at once by\n",
+ "calling the `.allInfo()` method. But you can also plot each chart individually\n",
+ "by calling any of the following methods:\n",
+ "- `.compareFlightTrajectories3D()`\n",
+ "- `.compareVelocities()`\n",
+ "- `.compareAerodynamicForces()`\n",
+ "- `.compareStaticMargins()`\n",
+ "- etc.\n",
+ "\n",
+ "You can use `dir(flight_plotter)`to check all the available methods."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Compare mode not yet implemented\n",
+ "Initial Conditions for Flight: Rocket - 1st Stage\n",
+ "Position - x: 0.00 m | y: 0.00 m | z: 1471.47 m\n",
+ "Velocity - Vx: 0.00 m/s | Vy: 0.00 m/s | Vz: 0.00 m/s\n",
+ "Attitude - e0: 0.975 | e1: -0.043 | e2: 0.009 | e3: -0.216\n",
+ "Euler Angles - Spin φ : 0.00° | Nutation θ: -5.00° | Precession ψ: -25.00°\n",
+ "Angular Velocity - ω1: 0.00 rad/s | ω2: 0.00 rad/s| ω3: 0.00 rad/s \n",
+ "\n",
+ "Initial Conditions for Flight: Rocket - 2nd Stage\n",
+ "Position - x: 39485.37 m | y: 133869.35 m | z: 4345914.80 m\n",
+ "Velocity - Vx: -191.43 m/s | Vy: -723.16 m/s | Vz: -47595.99 m/s\n",
+ "Attitude - e0: -67496.659 | e1: 63232.013 | e2: -4205.673 | e3: 15122.327\n",
+ "Euler Angles - Spin φ : -8.82° | Nutation θ: -84.72° | Precession ψ: -16.43°\n",
+ "Angular Velocity - ω1: -0.35 rad/s | ω2: -0.05 rad/s| ω3: 0.00 rad/s \n",
+ "\n",
+ "Initial Conditions for Flight: Payload\n",
+ "Position - x: 51501.13 m | y: 174607.13 m | z: 4349500.80 m\n",
+ "Velocity - Vx: -176.89 m/s | Vy: -685.68 m/s | Vz: -62960.94 m/s\n",
+ "Attitude - e0: -67498.155 | e1: 63230.993 | e2: -4205.605 | e3: 15122.661\n",
+ "Euler Angles - Spin φ : -8.82° | Nutation θ: -84.72° | Precession ψ: -16.43°\n",
+ "Angular Velocity - ω1: -0.35 rad/s | ω2: -0.05 rad/s| ω3: 0.00 rad/s \n",
+ "\n",
+ "Surface Wind Conditions of Flight: Rocket - 1st Stage\n",
+ "Frontal Surface Wind Speed: -3.72 m/s\n",
+ "Lateral Surface Wind Speed: -1.54 m/s\n",
+ "Surface Wind Conditions of Flight: Rocket - 2nd Stage\n",
+ "Frontal Surface Wind Speed: -4.02 m/s\n",
+ "Lateral Surface Wind Speed: 0.17 m/s\n",
+ "Surface Wind Conditions of Flight: Payload\n",
+ "Frontal Surface Wind Speed: -4.02 m/s\n",
+ "Lateral Surface Wind Speed: 0.17 m/s\n",
+ "Launch Rail Orientation of Flight: Rocket - 1st Stage\n",
+ "Launch Rail Inclination: 85.00°\n",
+ "Launch Rail Heading: 25.00°\n",
+ "\n",
+ "\n",
+ "Launch Rail Orientation of Flight: Rocket - 2nd Stage\n",
+ "Launch Rail Inclination: 0.00°\n",
+ "Launch Rail Heading: 0.00°\n",
+ "\n",
+ "\n",
+ "Launch Rail Orientation of Flight: Payload\n",
+ "Launch Rail Inclination: 0.00°\n",
+ "Launch Rail Heading: 0.00°\n",
+ "\n",
+ "\n",
+ "Rail Departure State of Flight: Rocket - 1st Stage\n",
+ "Rail Departure Time: 0.415 s\n",
+ "Rail Departure Velocity: 30.476 m/s\n",
+ "Rail Departure Static Margin: 2.148 c\n",
+ "Rail Departure Angle of Attack: 7.440°\n",
+ "Rail Departure Thrust-Weight Ratio: 10.308\n",
+ "Rail Departure Reynolds Number: 2.292e+05\n",
+ "Rail Departure State of Flight: Rocket - 2nd Stage\n",
+ "Rail Departure Time: 25.834 s\n",
+ "Rail Departure Velocity: 0.000 m/s\n",
+ "Rail Departure Static Margin: -0.000 c\n",
+ "Rail Departure Angle of Attack: 5.277°\n",
+ "Rail Departure Thrust-Weight Ratio: 0.000\n",
+ "Rail Departure Reynolds Number: 1.578e+05\n",
+ "Rail Departure State of Flight: Payload\n",
+ "Rail Departure Time: 25.834 s\n",
+ "Rail Departure Velocity: 0.000 m/s\n",
+ "Rail Departure Static Margin: -0.000 c\n",
+ "Rail Departure Angle of Attack: 5.277°\n",
+ "Rail Departure Thrust-Weight Ratio: 0.000\n",
+ "Rail Departure Reynolds Number: 1.578e+05\n",
+ "BurnOut State of Flight: Rocket - 1st Stage\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 654.629 m (AGL)\n",
+ "Rocket velocity at burnOut: 280.268 m/s\n",
+ "Freestream velocity at burnOut: 280.291 m/s\n",
+ "Mach Number at burnOut: 0.831\n",
+ "Kinetic energy at burnOut: 6.379e+05 J\n",
+ "BurnOut State of Flight: Rocket - 2nd Stage\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 2660233.688 m (AGL)\n",
+ "Rocket velocity at burnOut: 30.180 m/s\n",
+ "Freestream velocity at burnOut: 28.069 m/s\n",
+ "Mach Number at burnOut: 0.086\n",
+ "Kinetic energy at burnOut: 5.347e+03 J\n",
+ "BurnOut State of Flight: Payload\n",
+ "BurnOut time: 3.900 s\n",
+ "Altitude at burnOut: 2662428.447 m (AGL)\n",
+ "Rocket velocity at burnOut: 30.180 m/s\n",
+ "Freestream velocity at burnOut: 28.069 m/s\n",
+ "Mach Number at burnOut: 0.086\n",
+ "Kinetic energy at burnOut: 2.050e+03 J\n",
+ "Apogee State of Flight: Rocket - 1st Stage\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "Apogee State of Flight: Rocket - 2nd Stage\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "Apogee State of Flight: Payload\n",
+ "Apogee Altitude: 4767.601 m (ASL) | 3296.135 m (AGL)\n",
+ "Apogee Time: 25.834 s\n",
+ "Apogee Freestream Speed: 28.069 m/s\n",
+ "Parachute Events of Flight: Rocket - 1st Stage\n",
+ "No Parachute Events Were Triggered.\n",
+ "Parachute Events of Flight: Rocket - 2nd Stage\n",
+ "Drogue Ejection Triggered at: 25.838 s\n",
+ "Drogue Parachute Inflated at: 27.338 s\n",
+ "Drogue Parachute Inflated with Freestream Speed of: 31.228 m/s\n",
+ "Drogue Parachute Inflated at Height of: 3285.072 m (AGL)\n",
+ "Main Ejection Triggered at: 160.562 s\n",
+ "Main Parachute Inflated at: 162.062 s\n",
+ "Main Parachute Inflated with Freestream Speed of: 18.171 m/s\n",
+ "Main Parachute Inflated at Height of: 701.231 m (AGL)\n",
+ "Parachute Events of Flight: Payload\n",
+ "Drogue Ejection Triggered at: 25.838 s\n",
+ "Drogue Parachute Inflated at: 27.338 s\n",
+ "Drogue Parachute Inflated with Freestream Speed of: 31.083 m/s\n",
+ "Drogue Parachute Inflated at Height of: 3285.082 m (AGL)\n",
+ "Main Ejection Triggered at: 177.676 s\n",
+ "Main Parachute Inflated at: 179.176 s\n",
+ "Main Parachute Inflated with Freestream Speed of: 16.153 m/s\n",
+ "Main Parachute Inflated at Height of: 704.169 m (AGL)\n",
+ "Impact Conditions of Flight: Rocket - 1st Stage\n",
+ "X Impact: 0.000 m\n",
+ "Y Impact: 0.000 m\n",
+ "Time of Impact: 25.834 s\n",
+ "Velocity at Impact: 0.000 m/s\n",
+ "Impact Conditions of Flight: Rocket - 2nd Stage\n",
+ "X Impact: 263.439 m\n",
+ "Y Impact: 902.739 m\n",
+ "Time of Impact: 285.440 s\n",
+ "Velocity at Impact: -5.548 m/s\n",
+ "Impact Conditions of Flight: Payload\n",
+ "X Impact: 270.859 m\n",
+ "Y Impact: 929.654 m\n",
+ "Time of Impact: 327.907 s\n",
+ "Velocity at Impact: -4.608 m/s\n",
+ "Maximum Values of Flight: Rocket - 1st Stage\n",
+ "Maximum Speed: 286.303 m/s at 3.38 s\n",
+ "Maximum Mach Number: 0.847 Mach at 3.39 s\n",
+ "Maximum Reynolds Number: 2.034e+06 at 3.33 s\n",
+ "Maximum Dynamic Pressure: 4.062e+04 Pa at 3.35 s\n",
+ "Maximum Acceleration: 105.099 m/s² at 0.15 s\n",
+ "Maximum Gs: 10.717 g at 0.15 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.383 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.383 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.383 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.383 N\n",
+ "Maximum Values of Flight: Rocket - 2nd Stage\n",
+ "Maximum Speed: 33.104 m/s at 27.34 s\n",
+ "Maximum Mach Number: 0.095 Mach at 27.34 s\n",
+ "Maximum Reynolds Number: 1.757e+05 at 27.34 s\n",
+ "Maximum Dynamic Pressure: 3.644e+02 Pa at 27.34 s\n",
+ "Maximum Acceleration: 40.250 m/s² at 162.06 s\n",
+ "Maximum Gs: 4.104 g at 162.06 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.000 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.000 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.000 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.000 N\n",
+ "Maximum Values of Flight: Payload\n",
+ "Maximum Speed: 32.959 m/s at 27.34 s\n",
+ "Maximum Mach Number: 0.095 Mach at 27.34 s\n",
+ "Maximum Reynolds Number: 1.749e+05 at 27.34 s\n",
+ "Maximum Dynamic Pressure: 3.611e+02 Pa at 27.34 s\n",
+ "Maximum Acceleration: 25.033 m/s² at 179.18 s\n",
+ "Maximum Gs: 2.553 g at 179.18 s\n",
+ "Maximum Upper Rail Button Normal Force: 0.000 N\n",
+ "Maximum Upper Rail Button Shear Force: 0.000 N\n",
+ "Maximum Lower Rail Button Normal Force: 0.000 N\n",
+ "Maximum Lower Rail Button Shear Force: 0.000 N\n",
+ "Numerical Integration Settings of Flight: Rocket - 1st Stage\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: True\n",
+ "Number of Time Steps Used: 505\n",
+ "Number of Derivative Functions Evaluation: 1518\n",
+ "Average Function Evaluations per Time Step: 3.005941\n",
+ "Numerical Integration Settings of Flight: Rocket - 2nd Stage\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: False\n",
+ "Number of Time Steps Used: 230\n",
+ "Number of Derivative Functions Evaluation: 743\n",
+ "Average Function Evaluations per Time Step: 3.230435\n",
+ "Numerical Integration Settings of Flight: Payload\n",
+ "Maximum Allowed Flight Time: 600.000000 s\n",
+ "Maximum Allowed Time Step: inf s\n",
+ "Minimum Allowed Time Step: 0.000000e+00 s\n",
+ "Relative Error Tolerance: 1e-06\n",
+ "Absolute Error Tolerance: [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 1e-06, 1e-06, 1e-06, 1e-06, 0.001, 0.001, 0.001]\n",
+ "Allow Event Overshoot: True\n",
+ "Terminate Simulation on Apogee: False\n",
+ "Number of Time Steps Used: 219\n",
+ "Number of Derivative Functions Evaluation: 639\n",
+ "Average Function Evaluations per Time Step: 2.917808\n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "flight_plotter.allInfo(mode=\"compare\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "NoneType"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(RocketFlight2.rocket.railButtons)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": "\n\n\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "flight_plotter.compareRailButtonsForces()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3.10.6 64-bit",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.6"
+ },
+ "orig_nbformat": 4,
+ "vscode": {
+ "interpreter": {
+ "hash": "26de051ba29f2982a8de78e945f0abaf191376122a1563185a90213a26c5da77"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
\ No newline at end of file
diff --git a/docs/user/installation.rst b/docs/user/installation.rst
index f5f22f824..f80f6e2a1 100644
--- a/docs/user/installation.rst
+++ b/docs/user/installation.rst
@@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins
.. code-block:: shell
- pip install rocketpy==0.12.0
+ pip install rocketpy==0.12.1
Optional Installation Method: ``conda``
diff --git a/rocketpy/EnvironmentAnalysis.py b/rocketpy/EnvironmentAnalysis.py
index 2ae57a231..4d24043c0 100644
--- a/rocketpy/EnvironmentAnalysis.py
+++ b/rocketpy/EnvironmentAnalysis.py
@@ -2040,7 +2040,7 @@ def init():
ax.set_ylabel("Probability")
ax.set_title("Wind Gust Distribution")
# ax.grid(True)
- return ln, *bar_container.patches, tx
+ return (ln, *bar_container.patches, tx)
# Define function which sets each animation frame
def update(frame):
@@ -2056,7 +2056,7 @@ def update(frame):
ln.set_data(xdata, ydata)
# Update hour text
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
- return ln, *bar_container.patches, tx
+ return (ln, *bar_container.patches, tx)
for frame in wind_gusts_at_given_hour.items():
update(frame)
@@ -2243,7 +2243,7 @@ def init():
label="SAcup wind speed constraints",
) # Plot SAcup wind speed constraints
- return ln, *bar_container.patches, tx
+ return (ln, *bar_container.patches, tx)
# Define function which sets each animation frame
def update(frame):
@@ -2261,7 +2261,7 @@ def update(frame):
ln.set_data(xdata, ydata)
# Update hour text
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
- return ln, *bar_container.patches, tx
+ return (ln, *bar_container.patches, tx)
for frame in surface_wind_speeds_at_given_hour.items():
update(frame)
diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py
index 9fe4d3d46..8da8da6ed 100644
--- a/rocketpy/Flight.py
+++ b/rocketpy/Flight.py
@@ -15,6 +15,7 @@
from scipy import integrate
from .Function import Function
+from .plots.flight_plots import flight_plots
class Flight:
@@ -260,7 +261,7 @@ class Flight:
Rocket's velocity magnitude in the horizontal (North-East)
plane in m/s as a function of time. Can be called or
accessed as array.
- Flight.Acceleration : Function
+ Flight.acceleration : Function
Rocket acceleration magnitude in m/s² relative to ground as a
function of time. Can be called or accessed as array.
Flight.maxAcceleration : float
@@ -1999,6 +2000,7 @@ def postProcess(self, interpolation="spline", extrapolation="natural"):
# Potential Energy
self.potentialEnergy = totalMass * self.env.g * self.z
self.potentialEnergy.setInputs("Time (s)")
+ self.potentialEnergy.setOutputs("Potential Energy (J)")
# Total Mechanical Energy
self.totalEnergy = self.kineticEnergy + self.potentialEnergy
self.totalEnergy.setOutputs("Total Mechanical Energy (J)")
@@ -2303,189 +2305,7 @@ def info(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of out of rail time
- outOfRailTimeIndexes = np.nonzero(self.x[:, 0] == self.outOfRailTime)
- outOfRailTimeIndex = (
- -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
- )
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- # Print surface wind conditions
- print("Surface Wind Conditions\n")
- print("Frontal Surface Wind Speed: {:.2f} m/s".format(self.frontalSurfaceWind))
- print("Lateral Surface Wind Speed: {:.2f} m/s".format(self.lateralSurfaceWind))
-
- # Print out of rail conditions
- print("\n\n Rail Departure State\n")
- print("Rail Departure Time: {:.3f} s".format(self.outOfRailTime))
- print("Rail Departure Velocity: {:.3f} m/s".format(self.outOfRailVelocity))
- print(
- "Rail Departure Static Margin: {:.3f} c".format(
- self.staticMargin(self.outOfRailTime)
- )
- )
- print(
- "Rail Departure Angle of Attack: {:.3f}°".format(
- self.angleOfAttack(self.outOfRailTime)
- )
- )
- print(
- "Rail Departure Thrust-Weight Ratio: {:.3f}".format(
- self.rocket.thrustToWeight(self.outOfRailTime)
- )
- )
- print(
- "Rail Departure Reynolds Number: {:.3e}".format(
- self.ReynoldsNumber(self.outOfRailTime)
- )
- )
-
- # Print burnOut conditions
- print("\n\nBurnOut State\n")
- print("BurnOut time: {:.3f} s".format(self.rocket.motor.burnOutTime))
- print(
- "Altitude at burnOut: {:.3f} m (AGL)".format(
- self.z(self.rocket.motor.burnOutTime) - self.env.elevation
- )
- )
- print(
- "Rocket velocity at burnOut: {:.3f} m/s".format(
- self.speed(self.rocket.motor.burnOutTime)
- )
- )
- print(
- "Freestream velocity at burnOut: {:.3f} m/s".format(
- (
- self.streamVelocityX(self.rocket.motor.burnOutTime) ** 2
- + self.streamVelocityY(self.rocket.motor.burnOutTime) ** 2
- + self.streamVelocityZ(self.rocket.motor.burnOutTime) ** 2
- )
- ** 0.5
- )
- )
- print(
- "Mach Number at burnOut: {:.3f}".format(
- self.MachNumber(self.rocket.motor.burnOutTime)
- )
- )
- print(
- "Kinetic energy at burnOut: {:.3e} J".format(
- self.kineticEnergy(self.rocket.motor.burnOutTime)
- )
- )
-
- # Print apogee conditions
- print("\n\nApogee\n")
- print(
- "Apogee Altitude: {:.3f} m (ASL) | {:.3f} m (AGL)".format(
- self.apogee, self.apogee - self.env.elevation
- )
- )
- print("Apogee Time: {:.3f} s".format(self.apogeeTime))
- print("Apogee Freestream Speed: {:.3f} m/s".format(self.apogeeFreestreamSpeed))
-
- # Print events registered
- print("\n\nEvents\n")
- if len(self.parachuteEvents) == 0:
- print("No Parachute Events Were Triggered.")
- for event in self.parachuteEvents:
- triggerTime = event[0]
- parachute = event[1]
- openTime = triggerTime + parachute.lag
- velocity = self.freestreamSpeed(openTime)
- altitude = self.z(openTime)
- name = parachute.name.title()
- print(name + " Ejection Triggered at: {:.3f} s".format(triggerTime))
- print(name + " Parachute Inflated at: {:.3f} s".format(openTime))
- print(
- name
- + " Parachute Inflated with Freestream Speed of: {:.3f} m/s".format(
- velocity
- )
- )
- print(
- name
- + " Parachute Inflated at Height of: {:.3f} m (AGL)".format(
- altitude - self.env.elevation
- )
- )
-
- # Print impact conditions
- if len(self.impactState) != 0:
- print("\n\nImpact\n")
- print("X Impact: {:.3f} m".format(self.xImpact))
- print("Y Impact: {:.3f} m".format(self.yImpact))
- print("Time of Impact: {:.3f} s".format(self.tFinal))
- print("Velocity at Impact: {:.3f} m/s".format(self.impactVelocity))
- elif self.terminateOnApogee is False:
- print("\n\nEnd of Simulation\n")
- print("Time: {:.3f} s".format(self.solution[-1][0]))
- print("Altitude: {:.3f} m".format(self.solution[-1][3]))
-
- # Print maximum values
- print("\n\nMaximum Values\n")
- print(
- "Maximum Speed: {:.3f} m/s at {:.2f} s".format(
- self.maxSpeed, self.maxSpeedTime
- )
- )
- print(
- "Maximum Mach Number: {:.3f} Mach at {:.2f} s".format(
- self.maxMachNumber, self.maxMachNumberTime
- )
- )
- print(
- "Maximum Reynolds Number: {:.3e} at {:.2f} s".format(
- self.maxReynoldsNumber, self.maxReynoldsNumberTime
- )
- )
- print(
- "Maximum Dynamic Pressure: {:.3e} Pa at {:.2f} s".format(
- self.maxDynamicPressure, self.maxDynamicPressureTime
- )
- )
- print(
- "Maximum Acceleration: {:.3f} m/s² at {:.2f} s".format(
- self.maxAcceleration, self.maxAccelerationTime
- )
- )
- print(
- "Maximum Gs: {:.3f} g at {:.2f} s".format(
- self.maxAcceleration / self.env.g, self.maxAccelerationTime
- )
- )
- print(
- "Maximum Upper Rail Button Normal Force: {:.3f} N".format(
- self.maxRailButton1NormalForce
- )
- )
- print(
- "Maximum Upper Rail Button Shear Force: {:.3f} N".format(
- self.maxRailButton1ShearForce
- )
- )
- print(
- "Maximum Lower Rail Button Normal Force: {:.3f} N".format(
- self.maxRailButton2NormalForce
- )
- )
- print(
- "Maximum Lower Rail Button Shear Force: {:.3f} N".format(
- self.maxRailButton2ShearForce
- )
- )
-
+ flight_plots([self]).info()
return None
def printInitialConditionsData(self):
@@ -2499,35 +2319,7 @@ def printInitialConditionsData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- print(
- "Position - x: {:.2f} m | y: {:.2f} m | z: {:.2f} m".format(
- self.x(0), self.y(0), self.z(0)
- )
- )
- print(
- "Velocity - Vx: {:.2f} m/s | Vy: {:.2f} m/s | Vz: {:.2f} m/s".format(
- self.vx(0), self.vy(0), self.vz(0)
- )
- )
- print(
- "Attitude - e0: {:.3f} | e1: {:.3f} | e2: {:.3f} | e3: {:.3f}".format(
- self.e0(0), self.e1(0), self.e2(0), self.e3(0)
- )
- )
- print(
- "Euler Angles - Spin φ : {:.2f}° | Nutation θ: {:.2f}° | Precession ψ: {:.2f}°".format(
- self.phi(0), self.theta(0), self.psi(0)
- )
- )
- print(
- "Angular Velocity - ω1: {:.2f} rad/s | ω2: {:.2f} rad/s| ω3: {:.2f} rad/s".format(
- self.w1(0), self.w2(0), self.w3(0)
- )
- )
+ flight_plots([self]).printInitialConditionsData()
return None
def printNumericalIntegrationSettings(self):
@@ -2541,23 +2333,7 @@ def printNumericalIntegrationSettings(self):
------
None
"""
- print("Maximum Allowed Flight Time: {:f} s".format(self.maxTime))
- print("Maximum Allowed Time Step: {:f} s".format(self.maxTimeStep))
- print("Minimum Allowed Time Step: {:e} s".format(self.minTimeStep))
- print("Relative Error Tolerance: ", self.rtol)
- print("Absolute Error Tolerance: ", self.atol)
- print("Allow Event Overshoot: ", self.timeOvershoot)
- print("Terminate Simulation on Apogee: ", self.terminateOnApogee)
- print("Number of Time Steps Used: ", len(self.timeSteps))
- print(
- "Number of Derivative Functions Evaluation: ",
- sum(self.functionEvaluationsPerTimeStep),
- )
- print(
- "Average Function Evaluations per Time Step: {:3f}".format(
- sum(self.functionEvaluationsPerTimeStep) / len(self.timeSteps)
- )
- )
+ flight_plots([self]).printNumericalIntegrationSettings()
return None
@@ -2615,50 +2391,7 @@ def plot3dTrajectory(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get max and min x and y
- maxZ = max(self.z[:, 1] - self.env.elevation)
- maxX = max(self.x[:, 1])
- minX = min(self.x[:, 1])
- maxY = max(self.y[:, 1])
- minY = min(self.y[:, 1])
- maxXY = max(maxX, maxY)
- minXY = min(minX, minY)
-
- # Create figure
- fig1 = plt.figure(figsize=(9, 9))
- ax1 = plt.subplot(111, projection="3d")
- ax1.plot(self.x[:, 1], self.y[:, 1], zs=0, zdir="z", linestyle="--")
- ax1.plot(
- self.x[:, 1],
- self.z[:, 1] - self.env.elevation,
- zs=minXY,
- zdir="y",
- linestyle="--",
- )
- ax1.plot(
- self.y[:, 1],
- self.z[:, 1] - self.env.elevation,
- zs=minXY,
- zdir="x",
- linestyle="--",
- )
- ax1.plot(
- self.x[:, 1], self.y[:, 1], self.z[:, 1] - self.env.elevation, linewidth="2"
- )
- ax1.scatter(0, 0, 0)
- ax1.set_xlabel("X - East (m)")
- ax1.set_ylabel("Y - North (m)")
- ax1.set_zlabel("Z - Altitude Above Ground Level (m)")
- ax1.set_title("Flight Trajectory")
- ax1.set_zlim3d([0, maxZ])
- ax1.set_ylim3d([minXY, maxXY])
- ax1.set_xlim3d([minXY, maxXY])
- ax1.view_init(15, 45)
- plt.show()
+ flight_plots([self]).plot3dTrajectory()
return None
@@ -2673,71 +2406,7 @@ def plotLinearKinematicsData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Velocity and acceleration plots
- fig2 = plt.figure(figsize=(9, 12))
-
- ax1 = plt.subplot(414)
- ax1.plot(self.vx[:, 0], self.vx[:, 1], color="#ff7f0e")
- ax1.set_xlim(0, self.tFinal)
- ax1.set_title("Velocity X | Acceleration X")
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Velocity X (m/s)", color="#ff7f0e")
- ax1.tick_params("y", colors="#ff7f0e")
- ax1.grid(True)
-
- ax1up = ax1.twinx()
- ax1up.plot(self.ax[:, 0], self.ax[:, 1], color="#1f77b4")
- ax1up.set_ylabel("Acceleration X (m/s²)", color="#1f77b4")
- ax1up.tick_params("y", colors="#1f77b4")
-
- ax2 = plt.subplot(413)
- ax2.plot(self.vy[:, 0], self.vy[:, 1], color="#ff7f0e")
- ax2.set_xlim(0, self.tFinal)
- ax2.set_title("Velocity Y | Acceleration Y")
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Velocity Y (m/s)", color="#ff7f0e")
- ax2.tick_params("y", colors="#ff7f0e")
- ax2.grid(True)
-
- ax2up = ax2.twinx()
- ax2up.plot(self.ay[:, 0], self.ay[:, 1], color="#1f77b4")
- ax2up.set_ylabel("Acceleration Y (m/s²)", color="#1f77b4")
- ax2up.tick_params("y", colors="#1f77b4")
-
- ax3 = plt.subplot(412)
- ax3.plot(self.vz[:, 0], self.vz[:, 1], color="#ff7f0e")
- ax3.set_xlim(0, self.tFinal)
- ax3.set_title("Velocity Z | Acceleration Z")
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel("Velocity Z (m/s)", color="#ff7f0e")
- ax3.tick_params("y", colors="#ff7f0e")
- ax3.grid(True)
-
- ax3up = ax3.twinx()
- ax3up.plot(self.az[:, 0], self.az[:, 1], color="#1f77b4")
- ax3up.set_ylabel("Acceleration Z (m/s²)", color="#1f77b4")
- ax3up.tick_params("y", colors="#1f77b4")
-
- ax4 = plt.subplot(411)
- ax4.plot(self.speed[:, 0], self.speed[:, 1], color="#ff7f0e")
- ax4.set_xlim(0, self.tFinal)
- ax4.set_title("Velocity Magnitude | Acceleration Magnitude")
- ax4.set_xlabel("Time (s)")
- ax4.set_ylabel("Velocity (m/s)", color="#ff7f0e")
- ax4.tick_params("y", colors="#ff7f0e")
- ax4.grid(True)
-
- ax4up = ax4.twinx()
- ax4up.plot(self.acceleration[:, 0], self.acceleration[:, 1], color="#1f77b4")
- ax4up.set_ylabel("Acceleration (m/s²)", color="#1f77b4")
- ax4up.tick_params("y", colors="#1f77b4")
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
+ flight_plots([self]).plotLinearKinematicsData()
return None
def plotAttitudeData(self):
@@ -2751,59 +2420,7 @@ def plotAttitudeData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- # Angular position plots
- fig3 = plt.figure(figsize=(9, 12))
-
- ax1 = plt.subplot(411)
- ax1.plot(self.e0[:, 0], self.e0[:, 1], label="$e_0$")
- ax1.plot(self.e1[:, 0], self.e1[:, 1], label="$e_1$")
- ax1.plot(self.e2[:, 0], self.e2[:, 1], label="$e_2$")
- ax1.plot(self.e3[:, 0], self.e3[:, 1], label="$e_3$")
- ax1.set_xlim(0, eventTime)
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Euler Parameters")
- ax1.set_title("Euler Parameters")
- ax1.legend()
- ax1.grid(True)
-
- ax2 = plt.subplot(412)
- ax2.plot(self.psi[:, 0], self.psi[:, 1])
- ax2.set_xlim(0, eventTime)
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("ψ (°)")
- ax2.set_title("Euler Precession Angle")
- ax2.grid(True)
-
- ax3 = plt.subplot(413)
- ax3.plot(self.theta[:, 0], self.theta[:, 1], label="θ - Nutation")
- ax3.set_xlim(0, eventTime)
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel("θ (°)")
- ax3.set_title("Euler Nutation Angle")
- ax3.grid(True)
-
- ax4 = plt.subplot(414)
- ax4.plot(self.phi[:, 0], self.phi[:, 1], label="φ - Spin")
- ax4.set_xlim(0, eventTime)
- ax4.set_xlabel("Time (s)")
- ax4.set_ylabel("φ (°)")
- ax4.set_title("Euler Spin Angle")
- ax4.grid(True)
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
+ flight_plots([self]).plotAttitudeData()
return None
@@ -2819,47 +2436,7 @@ def plotFlightPathAngleData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- # Path, Attitude and Lateral Attitude Angle
- # Angular position plots
- fig5 = plt.figure(figsize=(9, 6))
-
- ax1 = plt.subplot(211)
- ax1.plot(self.pathAngle[:, 0], self.pathAngle[:, 1], label="Flight Path Angle")
- ax1.plot(
- self.attitudeAngle[:, 0],
- self.attitudeAngle[:, 1],
- label="Rocket Attitude Angle",
- )
- ax1.set_xlim(0, eventTime)
- ax1.legend()
- ax1.grid(True)
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Angle (°)")
- ax1.set_title("Flight Path and Attitude Angle")
-
- ax2 = plt.subplot(212)
- ax2.plot(self.lateralAttitudeAngle[:, 0], self.lateralAttitudeAngle[:, 1])
- ax2.set_xlim(0, eventTime)
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Lateral Attitude Angle (°)")
- ax2.set_title("Lateral Attitude Angle")
- ax2.grid(True)
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
-
+ flight_plots([self]).plotFlightPathAngleData()
return None
def plotAngularKinematicsData(self):
@@ -2875,76 +2452,7 @@ def plotAngularKinematicsData(self):
None
"""
# Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- # Angular velocity and acceleration plots
- fig4 = plt.figure(figsize=(9, 9))
- ax1 = plt.subplot(311)
- ax1.plot(self.w1[:, 0], self.w1[:, 1], color="#ff7f0e")
- ax1.set_xlim(0, eventTime)
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel(r"Angular Velocity - ${\omega_1}$ (rad/s)", color="#ff7f0e")
- ax1.set_title(
- r"Angular Velocity ${\omega_1}$ | Angular Acceleration ${\alpha_1}$"
- )
- ax1.tick_params("y", colors="#ff7f0e")
- ax1.grid(True)
-
- ax1up = ax1.twinx()
- ax1up.plot(self.alpha1[:, 0], self.alpha1[:, 1], color="#1f77b4")
- ax1up.set_ylabel(
- r"Angular Acceleration - ${\alpha_1}$ (rad/s²)", color="#1f77b4"
- )
- ax1up.tick_params("y", colors="#1f77b4")
-
- ax2 = plt.subplot(312)
- ax2.plot(self.w2[:, 0], self.w2[:, 1], color="#ff7f0e")
- ax2.set_xlim(0, eventTime)
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel(r"Angular Velocity - ${\omega_2}$ (rad/s)", color="#ff7f0e")
- ax2.set_title(
- r"Angular Velocity ${\omega_2}$ | Angular Acceleration ${\alpha_2}$"
- )
- ax2.tick_params("y", colors="#ff7f0e")
- ax2.grid(True)
-
- ax2up = ax2.twinx()
- ax2up.plot(self.alpha2[:, 0], self.alpha2[:, 1], color="#1f77b4")
- ax2up.set_ylabel(
- r"Angular Acceleration - ${\alpha_2}$ (rad/s²)", color="#1f77b4"
- )
- ax2up.tick_params("y", colors="#1f77b4")
-
- ax3 = plt.subplot(313)
- ax3.plot(self.w3[:, 0], self.w3[:, 1], color="#ff7f0e")
- ax3.set_xlim(0, eventTime)
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel(r"Angular Velocity - ${\omega_3}$ (rad/s)", color="#ff7f0e")
- ax3.set_title(
- r"Angular Velocity ${\omega_3}$ | Angular Acceleration ${\alpha_3}$"
- )
- ax3.tick_params("y", colors="#ff7f0e")
- ax3.grid(True)
-
- ax3up = ax3.twinx()
- ax3up.plot(self.alpha3[:, 0], self.alpha3[:, 1], color="#1f77b4")
- ax3up.set_ylabel(
- r"Angular Acceleration - ${\alpha_3}$ (rad/s²)", color="#1f77b4"
- )
- ax3up.tick_params("y", colors="#1f77b4")
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
-
+ flight_plots([self]).plotAngularKinematicsData()
return None
def plotTrajectoryForceData(self):
@@ -2958,128 +2466,7 @@ def plotTrajectoryForceData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of out of rail time
- outOfRailTimeIndexes = np.nonzero(self.x[:, 0] == self.outOfRailTime)
- outOfRailTimeIndex = (
- -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
- )
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- # Rail Button Forces
- if self.rocket.railButtons is not None:
- fig6 = plt.figure(figsize=(9, 6))
-
- ax1 = plt.subplot(211)
- ax1.plot(
- self.railButton1NormalForce[:outOfRailTimeIndex, 0],
- self.railButton1NormalForce[:outOfRailTimeIndex, 1],
- label="Upper Rail Button",
- )
- ax1.plot(
- self.railButton2NormalForce[:outOfRailTimeIndex, 0],
- self.railButton2NormalForce[:outOfRailTimeIndex, 1],
- label="Lower Rail Button",
- )
- ax1.set_xlim(
- 0, self.outOfRailTime if self.outOfRailTime > 0 else self.tFinal
- )
- ax1.legend()
- ax1.grid(True)
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Normal Force (N)")
- ax1.set_title("Rail Buttons Normal Force")
-
- ax2 = plt.subplot(212)
- ax2.plot(
- self.railButton1ShearForce[:outOfRailTimeIndex, 0],
- self.railButton1ShearForce[:outOfRailTimeIndex, 1],
- label="Upper Rail Button",
- )
- ax2.plot(
- self.railButton2ShearForce[:outOfRailTimeIndex, 0],
- self.railButton2ShearForce[:outOfRailTimeIndex, 1],
- label="Lower Rail Button",
- )
- ax2.set_xlim(
- 0, self.outOfRailTime if self.outOfRailTime > 0 else self.tFinal
- )
- ax2.legend()
- ax2.grid(True)
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Shear Force (N)")
- ax2.set_title("Rail Buttons Shear Force")
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
-
- # Aerodynamic force and moment plots
- fig7 = plt.figure(figsize=(9, 12))
-
- ax1 = plt.subplot(411)
- ax1.plot(
- self.aerodynamicLift[:eventTimeIndex, 0],
- self.aerodynamicLift[:eventTimeIndex, 1],
- label="Resultant",
- )
- ax1.plot(self.R1[:eventTimeIndex, 0], self.R1[:eventTimeIndex, 1], label="R1")
- ax1.plot(self.R2[:eventTimeIndex, 0], self.R2[:eventTimeIndex, 1], label="R2")
- ax1.set_xlim(0, eventTime)
- ax1.legend()
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Lift Force (N)")
- ax1.set_title("Aerodynamic Lift Resultant Force")
- ax1.grid()
-
- ax2 = plt.subplot(412)
- ax2.plot(
- self.aerodynamicDrag[:eventTimeIndex, 0],
- self.aerodynamicDrag[:eventTimeIndex, 1],
- )
- ax2.set_xlim(0, eventTime)
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Drag Force (N)")
- ax2.set_title("Aerodynamic Drag Force")
- ax2.grid()
-
- ax3 = plt.subplot(413)
- ax3.plot(
- self.aerodynamicBendingMoment[:eventTimeIndex, 0],
- self.aerodynamicBendingMoment[:eventTimeIndex, 1],
- label="Resultant",
- )
- ax3.plot(self.M1[:eventTimeIndex, 0], self.M1[:eventTimeIndex, 1], label="M1")
- ax3.plot(self.M2[:eventTimeIndex, 0], self.M2[:eventTimeIndex, 1], label="M2")
- ax3.set_xlim(0, eventTime)
- ax3.legend()
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel("Bending Moment (N m)")
- ax3.set_title("Aerodynamic Bending Resultant Moment")
- ax3.grid()
-
- ax4 = plt.subplot(414)
- ax4.plot(
- self.aerodynamicSpinMoment[:eventTimeIndex, 0],
- self.aerodynamicSpinMoment[:eventTimeIndex, 1],
- )
- ax4.set_xlim(0, eventTime)
- ax4.set_xlabel("Time (s)")
- ax4.set_ylabel("Spin Moment (N m)")
- ax4.set_title("Aerodynamic Spin Moment")
- ax4.grid()
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
+ flight_plots([self]).plotTrajectoryForceData()
return None
@@ -3090,90 +2477,7 @@ def plotEnergyData(self):
-------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of out of rail time
- outOfRailTimeIndexes = np.nonzero(self.x[:, 0] == self.outOfRailTime)
- outOfRailTimeIndex = (
- -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
- )
-
- # Get index of time before parachute event
- if len(self.parachuteEvents) > 0:
- eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag
- eventTimeIndex = np.nonzero(self.x[:, 0] == eventTime)[0][0]
- else:
- eventTime = self.tFinal
- eventTimeIndex = -1
-
- fig8 = plt.figure(figsize=(9, 9))
-
- ax1 = plt.subplot(411)
- ax1.plot(
- self.kineticEnergy[:, 0], self.kineticEnergy[:, 1], label="Kinetic Energy"
- )
- ax1.plot(
- self.rotationalEnergy[:, 0],
- self.rotationalEnergy[:, 1],
- label="Rotational Energy",
- )
- ax1.plot(
- self.translationalEnergy[:, 0],
- self.translationalEnergy[:, 1],
- label="Translational Energy",
- )
- ax1.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal)
- ax1.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax1.set_title("Kinetic Energy Components")
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Energy (J)")
-
- ax1.legend()
- ax1.grid()
-
- ax2 = plt.subplot(412)
- ax2.plot(self.totalEnergy[:, 0], self.totalEnergy[:, 1], label="Total Energy")
- ax2.plot(
- self.kineticEnergy[:, 0], self.kineticEnergy[:, 1], label="Kinetic Energy"
- )
- ax2.plot(
- self.potentialEnergy[:, 0],
- self.potentialEnergy[:, 1],
- label="Potential Energy",
- )
- ax2.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal)
- ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax2.set_title("Total Mechanical Energy Components")
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Energy (J)")
- ax2.legend()
- ax2.grid()
-
- ax3 = plt.subplot(413)
- ax3.plot(self.thrustPower[:, 0], self.thrustPower[:, 1], label="|Thrust Power|")
- ax3.set_xlim(0, self.rocket.motor.burnOutTime)
- ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax3.set_title("Thrust Absolute Power")
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel("Power (W)")
- ax3.legend()
- ax3.grid()
-
- ax4 = plt.subplot(414)
- ax4.plot(self.dragPower[:, 0], -self.dragPower[:, 1], label="|Drag Power|")
- ax4.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal)
- ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax4.set_title("Drag Absolute Power")
- ax4.set_xlabel("Time (s)")
- ax4.set_ylabel("Power (W)")
- ax4.legend()
- ax4.grid()
-
- plt.subplots_adjust(hspace=1)
- plt.show()
-
+ flight_plots([self]).plotEnergyData()
return None
def plotFluidMechanicsData(self):
@@ -3188,67 +2492,7 @@ def plotFluidMechanicsData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Get index of out of rail time
- outOfRailTimeIndexes = np.nonzero(self.x[:, 0] == self.outOfRailTime)
- outOfRailTimeIndex = (
- -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
- )
-
- # Trajectory Fluid Mechanics Plots
- fig10 = plt.figure(figsize=(9, 12))
-
- ax1 = plt.subplot(411)
- ax1.plot(self.MachNumber[:, 0], self.MachNumber[:, 1])
- ax1.set_xlim(0, self.tFinal)
- ax1.set_title("Mach Number")
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Mach Number")
- ax1.grid()
-
- ax2 = plt.subplot(412)
- ax2.plot(self.ReynoldsNumber[:, 0], self.ReynoldsNumber[:, 1])
- ax2.set_xlim(0, self.tFinal)
- ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax2.set_title("Reynolds Number")
- ax2.set_xlabel("Time (s)")
- ax2.set_ylabel("Reynolds Number")
- ax2.grid()
-
- ax3 = plt.subplot(413)
- ax3.plot(
- self.dynamicPressure[:, 0],
- self.dynamicPressure[:, 1],
- label="Dynamic Pressure",
- )
- ax3.plot(
- self.totalPressure[:, 0], self.totalPressure[:, 1], label="Total Pressure"
- )
- ax3.plot(self.pressure[:, 0], self.pressure[:, 1], label="Static Pressure")
- ax3.set_xlim(0, self.tFinal)
- ax3.legend()
- ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
- ax3.set_title("Total and Dynamic Pressure")
- ax3.set_xlabel("Time (s)")
- ax3.set_ylabel("Pressure (Pa)")
- ax3.grid()
-
- ax4 = plt.subplot(414)
- ax4.plot(self.angleOfAttack[:, 0], self.angleOfAttack[:, 1])
- # Make sure bottom and top limits are different
- if self.outOfRailTime * self.angleOfAttack(self.outOfRailTime) != 0:
- ax4.set_xlim(self.outOfRailTime, 10 * self.outOfRailTime + 1)
- ax4.set_ylim(0, self.angleOfAttack(self.outOfRailTime))
- ax4.set_title("Angle of Attack")
- ax4.set_xlabel("Time (s)")
- ax4.set_ylabel("Angle of Attack (°)")
- ax4.grid()
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
+ flight_plots([self]).plotFluidMechanicsData()
return None
@@ -3261,6 +2505,7 @@ def calculateFinFlutterAnalysis(self, finThickness, shearModulus):
not be useful for fins made from non-isotropic materials. These results
should not be used as a way to fully prove the safety of any rocket’s fins.
IMPORTANT: This function works if only a single set of fins is added
+ TODO: Separate this into calculation and plot, the method is too large.
Parameters
----------
@@ -3421,58 +2666,7 @@ def plotStabilityAndControlData(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- fig9 = plt.figure(figsize=(9, 6))
-
- ax1 = plt.subplot(211)
- ax1.plot(self.staticMargin[:, 0], self.staticMargin[:, 1])
- ax1.set_xlim(0, self.staticMargin[:, 0][-1])
- ax1.set_title("Static Margin")
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Static Margin (c)")
- ax1.grid()
-
- ax2 = plt.subplot(212)
- maxAttitude = max(self.attitudeFrequencyResponse[:, 1])
- maxAttitude = maxAttitude if maxAttitude != 0 else 1
- ax2.plot(
- self.attitudeFrequencyResponse[:, 0],
- self.attitudeFrequencyResponse[:, 1] / maxAttitude,
- label="Attitude Angle",
- )
- maxOmega1 = max(self.omega1FrequencyResponse[:, 1])
- maxOmega1 = maxOmega1 if maxOmega1 != 0 else 1
- ax2.plot(
- self.omega1FrequencyResponse[:, 0],
- self.omega1FrequencyResponse[:, 1] / maxOmega1,
- label=r"$\omega_1$",
- )
- maxOmega2 = max(self.omega2FrequencyResponse[:, 1])
- maxOmega2 = maxOmega2 if maxOmega2 != 0 else 1
- ax2.plot(
- self.omega2FrequencyResponse[:, 0],
- self.omega2FrequencyResponse[:, 1] / maxOmega2,
- label=r"$\omega_2$",
- )
- maxOmega3 = max(self.omega3FrequencyResponse[:, 1])
- maxOmega3 = maxOmega3 if maxOmega3 != 0 else 1
- ax2.plot(
- self.omega3FrequencyResponse[:, 0],
- self.omega3FrequencyResponse[:, 1] / maxOmega3,
- label=r"$\omega_3$",
- )
- ax2.set_title("Frequency Response")
- ax2.set_xlabel("Frequency (Hz)")
- ax2.set_ylabel("Amplitude Magnitude Normalized")
- ax2.set_xlim(0, 5)
- ax2.legend()
- ax2.grid()
-
- plt.subplots_adjust(hspace=0.5)
- plt.show()
+ flight_plots([self]).plotStabilityAndControlData()
return None
@@ -3495,31 +2689,12 @@ def plotPressureSignals(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- if len(self.rocket.parachutes) == 0:
- plt.figure()
- ax1 = plt.subplot(111)
- ax1.plot(self.z[:, 0], self.env.pressure(self.z[:, 1]))
- ax1.set_title("Pressure at Rocket's Altitude")
- ax1.set_xlabel("Time (s)")
- ax1.set_ylabel("Pressure (Pa)")
- ax1.set_xlim(0, self.tFinal)
- ax1.grid()
-
- plt.show()
-
- else:
- for parachute in self.rocket.parachutes:
- print("Parachute: ", parachute.name)
- parachute.noiseSignalFunction()
- parachute.noisyPressureSignalFunction()
- parachute.cleanPressureSignalFunction()
+ flight_plots([self]).plotPressureSignals()
return None
+ # Comment: Maybe we need a file for exportFlight methods...
+
def exportPressures(self, fileName, timeStep):
"""Exports the pressure experienced by the rocket during the flight to
an external file, the '.csv' format is recommended, as the columns will
@@ -3758,51 +2933,7 @@ def allInfo(self):
------
None
"""
- # Post-process results
- if self.postProcessed is False:
- self.postProcess()
-
- # Print initial conditions
- print("Initial Conditions\n")
- self.printInitialConditionsData()
-
- # Print launch rail orientation
- print("\n\nLaunch Rail Orientation\n")
- print("Launch Rail Inclination: {:.2f}°".format(self.inclination))
- print("Launch Rail Heading: {:.2f}°\n\n".format(self.heading))
-
- # Print a summary of data about the flight
- self.info()
-
- print("\n\nNumerical Integration Information\n")
- self.printNumericalIntegrationSettings()
-
- print("\n\nTrajectory 3d Plot\n")
- self.plot3dTrajectory()
-
- print("\n\nTrajectory Kinematic Plots\n")
- self.plotLinearKinematicsData()
-
- print("\n\nAngular Position Plots\n")
- self.plotFlightPathAngleData()
-
- print("\n\nPath, Attitude and Lateral Attitude Angle plots\n")
- self.plotAttitudeData()
-
- print("\n\nTrajectory Angular Velocity and Acceleration Plots\n")
- self.plotAngularKinematicsData()
-
- print("\n\nTrajectory Force Plots\n")
- self.plotTrajectoryForceData()
-
- print("\n\nTrajectory Energy Plots\n")
- self.plotEnergyData()
-
- print("\n\nTrajectory Fluid Mechanics Plots\n")
- self.plotFluidMechanicsData()
-
- print("\n\nTrajectory Stability and Control Plots\n")
- self.plotStabilityAndControlData()
+ flight_plots([self]).allInfo()
return None
@@ -3810,62 +2941,9 @@ def animate(self, start=0, stop=None, fps=12, speed=4, elev=None, azim=None):
"""Plays an animation of the flight. Not implemented yet. Only
kinda works outside notebook.
"""
- # Set up stopping time
- stop = self.tFinal if stop is None else stop
- # Speed = 4 makes it almost real time - matplotlib is way to slow
- # Set up graph
- fig = plt.figure(figsize=(18, 15))
- axes = fig.gca(projection="3d")
- # Initialize time
- timeRange = np.linspace(start, stop, fps * (stop - start))
- # Initialize first frame
- axes.set_title("Trajectory and Velocity Animation")
- axes.set_xlabel("X (m)")
- axes.set_ylabel("Y (m)")
- axes.set_zlabel("Z (m)")
- axes.view_init(elev, azim)
- R = axes.quiver(0, 0, 0, 0, 0, 0, color="r", label="Rocket")
- V = axes.quiver(0, 0, 0, 0, 0, 0, color="g", label="Velocity")
- W = axes.quiver(0, 0, 0, 0, 0, 0, color="b", label="Wind")
- S = axes.quiver(0, 0, 0, 0, 0, 0, color="black", label="Freestream")
- axes.legend()
- # Animate
- for t in timeRange:
- R.remove()
- V.remove()
- W.remove()
- S.remove()
- # Calculate rocket position
- Rx, Ry, Rz = self.x(t), self.y(t), self.z(t)
- Ru = 1 * (2 * (self.e1(t) * self.e3(t) + self.e0(t) * self.e2(t)))
- Rv = 1 * (2 * (self.e2(t) * self.e3(t) - self.e0(t) * self.e1(t)))
- Rw = 1 * (1 - 2 * (self.e1(t) ** 2 + self.e2(t) ** 2))
- # Calculate rocket Mach number
- Vx = self.vx(t) / 340.40
- Vy = self.vy(t) / 340.40
- Vz = self.vz(t) / 340.40
- # Calculate wind Mach Number
- z = self.z(t)
- Wx = self.env.windVelocityX(z) / 20
- Wy = self.env.windVelocityY(z) / 20
- # Calculate freestream Mach Number
- Sx = self.streamVelocityX(t) / 340.40
- Sy = self.streamVelocityY(t) / 340.40
- Sz = self.streamVelocityZ(t) / 340.40
- # Plot Quivers
- R = axes.quiver(Rx, Ry, Rz, Ru, Rv, Rw, color="r")
- V = axes.quiver(Rx, Ry, Rz, -Vx, -Vy, -Vz, color="g")
- W = axes.quiver(Rx - Vx, Ry - Vy, Rz - Vz, Wx, Wy, 0, color="b")
- S = axes.quiver(Rx, Ry, Rz, Sx, Sy, Sz, color="black")
- # Adjust axis
- axes.set_xlim(Rx - 1, Rx + 1)
- axes.set_ylim(Ry - 1, Ry + 1)
- axes.set_zlim(Rz - 1, Rz + 1)
- # plt.pause(1/(fps*speed))
- try:
- plt.pause(1 / (fps * speed))
- except:
- time.sleep(1 / (fps * speed))
+ flight_plots([self]).animate(self, start, stop, fps, speed, elev, azim)
+
+ return None
def timeIterator(self, nodeList):
i = 0
diff --git a/rocketpy/__init__.py b/rocketpy/__init__.py
index ecca8a231..7aa6f0454 100644
--- a/rocketpy/__init__.py
+++ b/rocketpy/__init__.py
@@ -16,7 +16,7 @@
__copyright__ = "Copyright 20XX, Projeto Jupiter"
__credits__ = ["Matheus Marques Araujo", "Rodrigo Schmitt", "Guilherme Tavares"]
__license__ = "MIT"
-__version__ = "0.12.0"
+__version__ = "0.12.1"
__maintainer__ = "Giovani Hidalgo Ceotto"
__email__ = "ghceotto@gmail.com"
__status__ = "Production"
diff --git a/rocketpy/plots/flight_plots.py b/rocketpy/plots/flight_plots.py
new file mode 100644
index 000000000..b7cc76b21
--- /dev/null
+++ b/rocketpy/plots/flight_plots.py
@@ -0,0 +1,3084 @@
+# -*- coding: utf-8 -*-
+
+import time
+import warnings
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+__author__ = "Guilherme Fernandes Alves"
+__copyright__ = "Copyright 20XX, RocketPy Team"
+__license__ = "MIT"
+
+## Future improvements:
+# Allow user to choose the units of the plots
+# Allow user to choose the color pallet of the plots
+# Add masses plots since it can vary significantly for multi-stage rockets
+
+
+class flight_plots:
+ """class to plot flight data
+ Here you also can:
+ - Print important information about the flight
+ - See animations of the flight
+ - Compare plots from different flights
+ - Compare flights from different rocket simulators
+ """
+
+ def __init__(
+ self,
+ trajectory_list,
+ names_list=None,
+ ):
+ """_summary_
+
+ Parameters
+ ----------
+ trajectory_list : list
+ List of Flight objects
+ names_list : list, optional
+ (the default is None, which [default_description])
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ self.names_list = names_list
+
+ if isinstance(trajectory_list, list):
+ self.trajectory_list = trajectory_list
+ # elif isinstance(trajectory_list, Flight):
+ # self.trajectory_list = [trajectory_list]
+ # self.names_list = [trajectory_list.__name__]
+ else:
+ raise TypeError("trajectory_list must be a list of Flight objects")
+
+ self.names_list = (
+ [("Trajectory " + str(i + 1)) for i in range(len(self.trajectory_list))]
+ if names_list == None
+ else names_list
+ )
+
+ # Start definition of Prints methods, no plots here for now
+
+ def printInitialConditionsData(self):
+ """Prints all initial conditions data available about the flights passed
+ by the trajectory_list.
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+
+ for index, flight in enumerate(self.trajectory_list):
+
+ print("\nInitial Conditions for Flight: ", self.names_list[index])
+
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print(
+ "Position - x: {:.2f} m | y: {:.2f} m | z: {:.2f} m".format(
+ flight.x(0), flight.y(0), flight.z(0)
+ )
+ )
+ print(
+ "Velocity - Vx: {:.2f} m/s | Vy: {:.2f} m/s | Vz: {:.2f} m/s".format(
+ flight.vx(0), flight.vy(0), flight.vz(0)
+ )
+ )
+ print(
+ "Attitude - e0: {:.3f} | e1: {:.3f} | e2: {:.3f} | e3: {:.3f}".format(
+ flight.e0(0), flight.e1(0), flight.e2(0), flight.e3(0)
+ )
+ )
+ print(
+ "Euler Angles - Spin φ : {:.2f}° | Nutation θ: {:.2f}° | Precession ψ: {:.2f}°".format(
+ flight.phi(0), flight.theta(0), flight.psi(0)
+ )
+ )
+ print(
+ "Angular Velocity - ω1: {:.2f} rad/s | ω2: {:.2f} rad/s| ω3: {:.2f} rad/s".format(
+ flight.w1(0), flight.w2(0), flight.w3(0)
+ )
+ )
+
+ return None
+
+ def printNumericalIntegrationSettings(self):
+ """Prints out the Numerical Integration settings available about the
+ flights passed by the trajectory_list.
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ print(
+ "\nNumerical Integration Settings of Flight: ", self.names_list[index]
+ )
+ print("Maximum Allowed Flight Time: {:f} s".format(flight.maxTime))
+ print("Maximum Allowed Time Step: {:f} s".format(flight.maxTimeStep))
+ print("Minimum Allowed Time Step: {:e} s".format(flight.minTimeStep))
+ print("Relative Error Tolerance: ", flight.rtol)
+ print("Absolute Error Tolerance: ", flight.atol)
+ print("Allow Event Overshoot: ", flight.timeOvershoot)
+ print("Terminate Simulation on Apogee: ", flight.terminateOnApogee)
+ print("Number of Time Steps Used: ", len(flight.timeSteps))
+ print(
+ "Number of Derivative Functions Evaluation: ",
+ sum(flight.functionEvaluationsPerTimeStep),
+ )
+ print(
+ "Average Function Evaluations per Time Step: {:3f}".format(
+ sum(flight.functionEvaluationsPerTimeStep) / len(flight.timeSteps)
+ )
+ )
+
+ return None
+
+ def printSurfaceWindConditions(self):
+ """Prints out the Surface Wind Conditions available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print("\nSurface Wind Conditions of Flight: ", self.names_list[index])
+ print(
+ "Frontal Surface Wind Speed: {:.2f} m/s".format(
+ flight.frontalSurfaceWind
+ )
+ )
+ print(
+ "Lateral Surface Wind Speed: {:.2f} m/s".format(
+ flight.lateralSurfaceWind
+ )
+ )
+
+ return None
+
+ def printLaunchRailConditions(self):
+ """Prints out the Launch Rail Conditions available about the flights
+ passed by the trajectory_list.
+
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ None
+ """
+
+ for index, flight in enumerate(self.trajectory_list):
+ print("\nLaunch Rail Orientation of Flight: ", self.names_list[index])
+ print("Launch Rail Inclination: {:.2f}°".format(flight.inclination))
+ print("Launch Rail Heading: {:.2f}°".format(flight.heading))
+ return None
+
+ def printOutOfRailConditions(self):
+ """Prints out the Out of Rail Conditions available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print("\nRail Departure State of Flight: ", self.names_list[index])
+ print("Rail Departure Time: {:.3f} s".format(flight.outOfRailTime))
+ print(
+ "Rail Departure Velocity: {:.3f} m/s".format(flight.outOfRailVelocity)
+ )
+ print(
+ "Rail Departure Static Margin: {:.3f} c".format(
+ flight.staticMargin(flight.outOfRailTime)
+ )
+ )
+ print(
+ "Rail Departure Angle of Attack: {:.3f}°".format(
+ flight.angleOfAttack(flight.outOfRailTime)
+ )
+ )
+ print(
+ "Rail Departure Thrust-Weight Ratio: {:.3f}".format(
+ flight.rocket.thrustToWeight(flight.outOfRailTime)
+ )
+ )
+ print(
+ "Rail Departure Reynolds Number: {:.3e}".format(
+ flight.ReynoldsNumber(flight.outOfRailTime)
+ )
+ )
+
+ return None
+
+ def printBurnOutConditions(self):
+ """Prints out the Burn Out Conditions available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print("\nBurnOut State of Flight: ", self.names_list[index])
+ print("BurnOut time: {:.3f} s".format(flight.rocket.motor.burnOutTime))
+ print(
+ "Altitude at burnOut: {:.3f} m (AGL)".format(
+ flight.z(flight.rocket.motor.burnOutTime) - flight.env.elevation
+ )
+ )
+ print(
+ "Rocket velocity at burnOut: {:.3f} m/s".format(
+ flight.speed(flight.rocket.motor.burnOutTime)
+ )
+ )
+ print(
+ "Freestream velocity at burnOut: {:.3f} m/s".format(
+ (
+ flight.streamVelocityX(flight.rocket.motor.burnOutTime) ** 2
+ + flight.streamVelocityY(flight.rocket.motor.burnOutTime) ** 2
+ + flight.streamVelocityZ(flight.rocket.motor.burnOutTime) ** 2
+ )
+ ** 0.5
+ )
+ )
+ print(
+ "Mach Number at burnOut: {:.3f}".format(
+ flight.MachNumber(flight.rocket.motor.burnOutTime)
+ )
+ )
+ print(
+ "Kinetic energy at burnOut: {:.3e} J".format(
+ flight.kineticEnergy(flight.rocket.motor.burnOutTime)
+ )
+ )
+
+ return None
+
+ def printApogeeConditions(self):
+ """Prints out the Apogee Conditions available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print("\nApogee State of Flight: ", self.names_list[index])
+ print(
+ "Apogee Altitude: {:.3f} m (ASL) | {:.3f} m (AGL)".format(
+ flight.apogee, flight.apogee - flight.env.elevation
+ )
+ )
+ print("Apogee Time: {:.3f} s".format(flight.apogeeTime))
+ print(
+ "Apogee Freestream Speed: {:.3f} m/s".format(
+ flight.apogeeFreestreamSpeed
+ )
+ )
+
+ return None
+
+ def printEventsRegistered(self):
+ """Prints out the Events Registered available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ print("\nParachute Events of Flight: ", self.names_list[index])
+ if len(flight.parachuteEvents) == 0:
+ print("No Parachute Events Were Triggered.")
+ for event in flight.parachuteEvents:
+ triggerTime = event[0]
+ parachute = event[1]
+ openTime = triggerTime + parachute.lag
+ velocity = flight.freestreamSpeed(openTime)
+ altitude = flight.z(openTime)
+ name = parachute.name.title()
+ print(name + " Ejection Triggered at: {:.3f} s".format(triggerTime))
+ print(name + " Parachute Inflated at: {:.3f} s".format(openTime))
+ print(
+ name
+ + " Parachute Inflated with Freestream Speed of: {:.3f} m/s".format(
+ velocity
+ )
+ )
+ print(
+ name
+ + " Parachute Inflated at Height of: {:.3f} m (AGL)".format(
+ altitude - flight.env.elevation
+ )
+ )
+ return None
+
+ def printImpactConditions(self):
+ """Prints out the Impact Conditions available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ if len(flight.impactState) != 0:
+ print("\nImpact Conditions of Flight: ", self.names_list[index])
+ print("X Impact: {:.3f} m".format(flight.xImpact))
+ print("Y Impact: {:.3f} m".format(flight.yImpact))
+ print("Time of Impact: {:.3f} s".format(flight.tFinal))
+ print("Velocity at Impact: {:.3f} m/s".format(flight.impactVelocity))
+ elif flight.terminateOnApogee is False:
+ print("End of Simulation of Flight: ", flight.names_list[index])
+ print("Time: {:.3f} s".format(flight.solution[-1][0]))
+ print("Altitude: {:.3f} m".format(flight.solution[-1][3]))
+
+ return None
+
+ def printMaximumValues(self):
+ """Prints out the Maximum Values available about the flights
+ passed by the trajectory_list.
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ print("\nMaximum Values of Flight: ", self.names_list[index])
+ print(
+ "Maximum Speed: {:.3f} m/s at {:.2f} s".format(
+ flight.maxSpeed, flight.maxSpeedTime
+ )
+ )
+ print(
+ "Maximum Mach Number: {:.3f} Mach at {:.2f} s".format(
+ flight.maxMachNumber, flight.maxMachNumberTime
+ )
+ )
+ print(
+ "Maximum Reynolds Number: {:.3e} at {:.2f} s".format(
+ flight.maxReynoldsNumber, flight.maxReynoldsNumberTime
+ )
+ )
+ print(
+ "Maximum Dynamic Pressure: {:.3e} Pa at {:.2f} s".format(
+ flight.maxDynamicPressure, flight.maxDynamicPressureTime
+ )
+ )
+ print(
+ "Maximum Acceleration: {:.3f} m/s² at {:.2f} s".format(
+ flight.maxAcceleration, flight.maxAccelerationTime
+ )
+ )
+ print(
+ "Maximum Gs: {:.3f} g at {:.2f} s".format(
+ flight.maxAcceleration / flight.env.g, flight.maxAccelerationTime
+ )
+ )
+ print(
+ "Maximum Upper Rail Button Normal Force: {:.3f} N".format(
+ flight.maxRailButton1NormalForce
+ )
+ )
+ print(
+ "Maximum Upper Rail Button Shear Force: {:.3f} N".format(
+ flight.maxRailButton1ShearForce
+ )
+ )
+ print(
+ "Maximum Lower Rail Button Normal Force: {:.3f} N".format(
+ flight.maxRailButton2NormalForce
+ )
+ )
+ print(
+ "Maximum Lower Rail Button Shear Force: {:.3f} N".format(
+ flight.maxRailButton2ShearForce
+ )
+ )
+ return None
+
+ # Start definition of 'basic' plots methods, the traditional RocketPy plots
+
+ def plot3dTrajectory(self, savefig=False):
+ """Plot a 3D graph of the trajectory
+
+ Parameters
+ ----------
+ savefig: str, optional
+ If a string is passed, the figure will be saved with the name passed.
+ Default is False.
+
+ Return
+ ------
+ None
+ """
+
+ warnings.warn(
+ "plot3dTrajectory is going to be deprecated, use compareFlightTrajectories3D instead.",
+ )
+ self.compareFlightTrajectories3D(legend=False, savefig=savefig)
+
+ return None
+
+ def plotLinearKinematicsData(self):
+ """Prints out all Kinematics graphs available about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+
+ for index, flight in enumerate(self.trajectory_list):
+
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Velocity and acceleration plots
+ fig2 = plt.figure(figsize=(9, 12))
+ fig2.suptitle(
+ "Linear Kinematics Data of Flight: {}".format(self.names_list[index])
+ )
+
+ ax1 = plt.subplot(414)
+ ax1.plot(flight.vx[:, 0], flight.vx[:, 1], color="#ff7f0e")
+ ax1.set_xlim(0, flight.tFinal)
+ ax1.set_title("Velocity X | Acceleration X")
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Velocity X (m/s)", color="#ff7f0e")
+ ax1.tick_params("y", colors="#ff7f0e")
+ ax1.grid(True)
+
+ ax1up = ax1.twinx()
+ ax1up.plot(flight.ax[:, 0], flight.ax[:, 1], color="#1f77b4")
+ ax1up.set_ylabel("Acceleration X (m/s²)", color="#1f77b4")
+ ax1up.tick_params("y", colors="#1f77b4")
+
+ ax2 = plt.subplot(413)
+ ax2.plot(flight.vy[:, 0], flight.vy[:, 1], color="#ff7f0e")
+ ax2.set_xlim(0, flight.tFinal)
+ ax2.set_title("Velocity Y | Acceleration Y")
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Velocity Y (m/s)", color="#ff7f0e")
+ ax2.tick_params("y", colors="#ff7f0e")
+ ax2.grid(True)
+
+ ax2up = ax2.twinx()
+ ax2up.plot(flight.ay[:, 0], flight.ay[:, 1], color="#1f77b4")
+ ax2up.set_ylabel("Acceleration Y (m/s²)", color="#1f77b4")
+ ax2up.tick_params("y", colors="#1f77b4")
+
+ ax3 = plt.subplot(412)
+ ax3.plot(flight.vz[:, 0], flight.vz[:, 1], color="#ff7f0e")
+ ax3.set_xlim(0, flight.tFinal)
+ ax3.set_title("Velocity Z | Acceleration Z")
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel("Velocity Z (m/s)", color="#ff7f0e")
+ ax3.tick_params("y", colors="#ff7f0e")
+ ax3.grid(True)
+
+ ax3up = ax3.twinx()
+ ax3up.plot(flight.az[:, 0], flight.az[:, 1], color="#1f77b4")
+ ax3up.set_ylabel("Acceleration Z (m/s²)", color="#1f77b4")
+ ax3up.tick_params("y", colors="#1f77b4")
+
+ ax4 = plt.subplot(411)
+ ax4.plot(flight.speed[:, 0], flight.speed[:, 1], color="#ff7f0e")
+ ax4.set_xlim(0, flight.tFinal)
+ ax4.set_title("Velocity Magnitude | Acceleration Magnitude")
+ ax4.set_xlabel("Time (s)")
+ ax4.set_ylabel("Velocity (m/s)", color="#ff7f0e")
+ ax4.tick_params("y", colors="#ff7f0e")
+ ax4.grid(True)
+
+ ax4up = ax4.twinx()
+ ax4up.plot(
+ flight.acceleration[:, 0], flight.acceleration[:, 1], color="#1f77b4"
+ )
+ ax4up.set_ylabel("Acceleration (m/s²)", color="#1f77b4")
+ ax4up.tick_params("y", colors="#1f77b4")
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+ return None
+
+ def plotAttitudeData(self):
+ """Prints out all Angular position graphs available about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+
+ for index, flight in enumerate(self.trajectory_list):
+
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of time before parachute event
+ if len(flight.parachuteEvents) > 0:
+ eventTime = (
+ flight.parachuteEvents[0][0] + flight.parachuteEvents[0][1].lag
+ )
+ eventTimeIndex = np.nonzero(flight.x[:, 0] == eventTime)[0][0]
+ else:
+ eventTime = flight.tFinal
+ eventTimeIndex = -1
+
+ # Angular position plots
+ fig3 = plt.figure(figsize=(9, 12))
+ fig3.suptitle("Euler Angles of Flight: {}".format(self.names_list[index]))
+
+ ax1 = plt.subplot(411)
+ ax1.plot(flight.e0[:, 0], flight.e0[:, 1], label="$e_0$")
+ ax1.plot(flight.e1[:, 0], flight.e1[:, 1], label="$e_1$")
+ ax1.plot(flight.e2[:, 0], flight.e2[:, 1], label="$e_2$")
+ ax1.plot(flight.e3[:, 0], flight.e3[:, 1], label="$e_3$")
+ ax1.set_xlim(0, eventTime)
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Euler Parameters")
+ ax1.set_title("Euler Parameters")
+ ax1.legend()
+ ax1.grid(True)
+
+ ax2 = plt.subplot(412)
+ ax2.plot(flight.psi[:, 0], flight.psi[:, 1])
+ ax2.set_xlim(0, eventTime)
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("ψ (°)")
+ ax2.set_title("Euler Precession Angle")
+ ax2.grid(True)
+
+ ax3 = plt.subplot(413)
+ ax3.plot(flight.theta[:, 0], flight.theta[:, 1], label="θ - Nutation")
+ ax3.set_xlim(0, eventTime)
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel("θ (°)")
+ ax3.set_title("Euler Nutation Angle")
+ ax3.grid(True)
+
+ ax4 = plt.subplot(414)
+ ax4.plot(flight.phi[:, 0], flight.phi[:, 1], label="φ - Spin")
+ ax4.set_xlim(0, eventTime)
+ ax4.set_xlabel("Time (s)")
+ ax4.set_ylabel("φ (°)")
+ ax4.set_title("Euler Spin Angle")
+ ax4.grid(True)
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotFlightPathAngleData(self):
+ """Prints out Flight path and Rocket Attitude angle graphs available
+ about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of time before parachute event
+ if len(flight.parachuteEvents) > 0:
+ eventTime = (
+ flight.parachuteEvents[0][0] + flight.parachuteEvents[0][1].lag
+ )
+ eventTimeIndex = np.nonzero(flight.x[:, 0] == eventTime)[0][0]
+ else:
+ eventTime = flight.tFinal
+ eventTimeIndex = -1
+
+ # Path, Attitude and Lateral Attitude Angle
+ # Angular position plots
+ fig5 = plt.figure(figsize=(9, 6))
+ fig5.suptitle(
+ "Flight Path and Attitude Data of Flight: {}".format(
+ self.names_list[index]
+ )
+ )
+
+ ax1 = plt.subplot(211)
+ ax1.plot(
+ flight.pathAngle[:, 0],
+ flight.pathAngle[:, 1],
+ label="Flight Path Angle",
+ )
+ ax1.plot(
+ flight.attitudeAngle[:, 0],
+ flight.attitudeAngle[:, 1],
+ label="Rocket Attitude Angle",
+ )
+ ax1.set_xlim(0, eventTime)
+ ax1.legend()
+ ax1.grid(True)
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Angle (°)")
+ ax1.set_title("Flight Path and Attitude Angle")
+
+ ax2 = plt.subplot(212)
+ ax2.plot(
+ flight.lateralAttitudeAngle[:, 0], flight.lateralAttitudeAngle[:, 1]
+ )
+ ax2.set_xlim(0, eventTime)
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Lateral Attitude Angle (°)")
+ ax2.set_title("Lateral Attitude Angle")
+ ax2.grid(True)
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotAngularKinematicsData(self):
+ """Prints out all Angular velocity and acceleration graphs available
+ about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+
+ for index, flight in enumerate(self.trajectory_list):
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of time before parachute event
+ if len(flight.parachuteEvents) > 0:
+ eventTime = (
+ flight.parachuteEvents[0][0] + flight.parachuteEvents[0][1].lag
+ )
+ eventTimeIndex = np.nonzero(flight.x[:, 0] == eventTime)[0][0]
+ else:
+ eventTime = flight.tFinal
+ eventTimeIndex = -1
+
+ # Angular velocity and acceleration plots
+ fig4 = plt.figure(figsize=(9, 9))
+ fig4.suptitle(
+ "Angular Kinematics Data of Flight: {}".format(self.names_list[index])
+ )
+
+ ax1 = plt.subplot(311)
+ ax1.plot(flight.w1[:, 0], flight.w1[:, 1], color="#ff7f0e")
+ ax1.set_xlim(0, eventTime)
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel(r"Angular Velocity - ${\omega_1}$ (rad/s)", color="#ff7f0e")
+ ax1.set_title(
+ r"Angular Velocity ${\omega_1}$ | Angular Acceleration ${\alpha_1}$"
+ )
+ ax1.tick_params("y", colors="#ff7f0e")
+ ax1.grid(True)
+
+ ax1up = ax1.twinx()
+ ax1up.plot(flight.alpha1[:, 0], flight.alpha1[:, 1], color="#1f77b4")
+ ax1up.set_ylabel(
+ r"Angular Acceleration - ${\alpha_1}$ (rad/s²)", color="#1f77b4"
+ )
+ ax1up.tick_params("y", colors="#1f77b4")
+
+ ax2 = plt.subplot(312)
+ ax2.plot(flight.w2[:, 0], flight.w2[:, 1], color="#ff7f0e")
+ ax2.set_xlim(0, eventTime)
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel(r"Angular Velocity - ${\omega_2}$ (rad/s)", color="#ff7f0e")
+ ax2.set_title(
+ r"Angular Velocity ${\omega_2}$ | Angular Acceleration ${\alpha_2}$"
+ )
+ ax2.tick_params("y", colors="#ff7f0e")
+ ax2.grid(True)
+
+ ax2up = ax2.twinx()
+ ax2up.plot(flight.alpha2[:, 0], flight.alpha2[:, 1], color="#1f77b4")
+ ax2up.set_ylabel(
+ r"Angular Acceleration - ${\alpha_2}$ (rad/s²)", color="#1f77b4"
+ )
+ ax2up.tick_params("y", colors="#1f77b4")
+
+ ax3 = plt.subplot(313)
+ ax3.plot(flight.w3[:, 0], flight.w3[:, 1], color="#ff7f0e")
+ ax3.set_xlim(0, eventTime)
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel(r"Angular Velocity - ${\omega_3}$ (rad/s)", color="#ff7f0e")
+ ax3.set_title(
+ r"Angular Velocity ${\omega_3}$ | Angular Acceleration ${\alpha_3}$"
+ )
+ ax3.tick_params("y", colors="#ff7f0e")
+ ax3.grid(True)
+
+ ax3up = ax3.twinx()
+ ax3up.plot(flight.alpha3[:, 0], flight.alpha3[:, 1], color="#1f77b4")
+ ax3up.set_ylabel(
+ r"Angular Acceleration - ${\alpha_3}$ (rad/s²)", color="#1f77b4"
+ )
+ ax3up.tick_params("y", colors="#1f77b4")
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotTrajectoryForceData(self):
+ """Prints out all Forces and Moments graphs available about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of out of rail time
+ outOfRailTimeIndexes = np.nonzero(flight.x[:, 0] == flight.outOfRailTime)
+ outOfRailTimeIndex = (
+ -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
+ )
+
+ # Get index of time before parachute event
+ if len(flight.parachuteEvents) > 0:
+ eventTime = (
+ flight.parachuteEvents[0][0] + flight.parachuteEvents[0][1].lag
+ )
+ eventTimeIndex = np.nonzero(flight.x[:, 0] == eventTime)[0][0]
+ else:
+ eventTime = flight.tFinal
+ eventTimeIndex = -1
+
+ # Rail Button Forces
+ if flight.rocket.railButtons is not None:
+ fig6 = plt.figure(figsize=(9, 6))
+ fig6.suptitle(
+ "Rail Button Forces of Flight: {}".format(self.names_list[index])
+ )
+
+ ax1 = plt.subplot(211)
+ ax1.plot(
+ flight.railButton1NormalForce[:outOfRailTimeIndex, 0],
+ flight.railButton1NormalForce[:outOfRailTimeIndex, 1],
+ label="Upper Rail Button",
+ )
+ ax1.plot(
+ flight.railButton2NormalForce[:outOfRailTimeIndex, 0],
+ flight.railButton2NormalForce[:outOfRailTimeIndex, 1],
+ label="Lower Rail Button",
+ )
+ ax1.set_xlim(
+ 0,
+ flight.outOfRailTime if flight.outOfRailTime > 0 else flight.tFinal,
+ )
+ ax1.legend()
+ ax1.grid(True)
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Normal Force (N)")
+ ax1.set_title("Rail Buttons Normal Force")
+
+ ax2 = plt.subplot(212)
+ ax2.plot(
+ flight.railButton1ShearForce[:outOfRailTimeIndex, 0],
+ flight.railButton1ShearForce[:outOfRailTimeIndex, 1],
+ label="Upper Rail Button",
+ )
+ ax2.plot(
+ flight.railButton2ShearForce[:outOfRailTimeIndex, 0],
+ flight.railButton2ShearForce[:outOfRailTimeIndex, 1],
+ label="Lower Rail Button",
+ )
+ ax2.set_xlim(
+ 0,
+ flight.outOfRailTime if flight.outOfRailTime > 0 else flight.tFinal,
+ )
+ ax2.legend()
+ ax2.grid(True)
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Shear Force (N)")
+ ax2.set_title("Rail Buttons Shear Force")
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ # Aerodynamic force and moment plots
+ fig7 = plt.figure(figsize=(9, 12))
+ fig7.suptitle(
+ "Aerodynamic Forces and Moments of Flight: {}".format(
+ self.names_list[index]
+ )
+ )
+
+ ax1 = plt.subplot(411)
+ ax1.plot(
+ flight.aerodynamicLift[:eventTimeIndex, 0],
+ flight.aerodynamicLift[:eventTimeIndex, 1],
+ label="Resultant",
+ )
+ ax1.plot(
+ flight.R1[:eventTimeIndex, 0], flight.R1[:eventTimeIndex, 1], label="R1"
+ )
+ ax1.plot(
+ flight.R2[:eventTimeIndex, 0], flight.R2[:eventTimeIndex, 1], label="R2"
+ )
+ ax1.set_xlim(0, eventTime)
+ ax1.legend()
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Lift Force (N)")
+ ax1.set_title("Aerodynamic Lift Resultant Force")
+ ax1.grid()
+
+ ax2 = plt.subplot(412)
+ ax2.plot(
+ flight.aerodynamicDrag[:eventTimeIndex, 0],
+ flight.aerodynamicDrag[:eventTimeIndex, 1],
+ )
+ ax2.set_xlim(0, eventTime)
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Drag Force (N)")
+ ax2.set_title("Aerodynamic Drag Force")
+ ax2.grid()
+
+ ax3 = plt.subplot(413)
+ ax3.plot(
+ flight.aerodynamicBendingMoment[:eventTimeIndex, 0],
+ flight.aerodynamicBendingMoment[:eventTimeIndex, 1],
+ label="Resultant",
+ )
+ ax3.plot(
+ flight.M1[:eventTimeIndex, 0], flight.M1[:eventTimeIndex, 1], label="M1"
+ )
+ ax3.plot(
+ flight.M2[:eventTimeIndex, 0], flight.M2[:eventTimeIndex, 1], label="M2"
+ )
+ ax3.set_xlim(0, eventTime)
+ ax3.legend()
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel("Bending Moment (N m)")
+ ax3.set_title("Aerodynamic Bending Resultant Moment")
+ ax3.grid()
+
+ ax4 = plt.subplot(414)
+ ax4.plot(
+ flight.aerodynamicSpinMoment[:eventTimeIndex, 0],
+ flight.aerodynamicSpinMoment[:eventTimeIndex, 1],
+ )
+ ax4.set_xlim(0, eventTime)
+ ax4.set_xlabel("Time (s)")
+ ax4.set_ylabel("Spin Moment (N m)")
+ ax4.set_title("Aerodynamic Spin Moment")
+ ax4.grid()
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotEnergyData(self):
+ """Prints out all Energy components graphs available about the Flight
+
+ Returns
+ -------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of out of rail time
+ outOfRailTimeIndexes = np.nonzero(flight.x[:, 0] == flight.outOfRailTime)
+ outOfRailTimeIndex = (
+ -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
+ )
+
+ # Get index of time before parachute event
+ if len(flight.parachuteEvents) > 0:
+ eventTime = (
+ flight.parachuteEvents[0][0] + flight.parachuteEvents[0][1].lag
+ )
+ eventTimeIndex = np.nonzero(flight.x[:, 0] == eventTime)[0][0]
+ else:
+ eventTime = flight.tFinal
+ eventTimeIndex = -1
+
+ fig8 = plt.figure(figsize=(9, 9))
+ fig8.suptitle(
+ "Energy Components of Flight: {}".format(self.names_list[index])
+ )
+
+ ax1 = plt.subplot(411)
+ ax1.plot(
+ flight.kineticEnergy[:, 0],
+ flight.kineticEnergy[:, 1],
+ label="Kinetic Energy",
+ )
+ ax1.plot(
+ flight.rotationalEnergy[:, 0],
+ flight.rotationalEnergy[:, 1],
+ label="Rotational Energy",
+ )
+ ax1.plot(
+ flight.translationalEnergy[:, 0],
+ flight.translationalEnergy[:, 1],
+ label="Translational Energy",
+ )
+ ax1.set_xlim(
+ 0, flight.apogeeTime if flight.apogeeTime != 0.0 else flight.tFinal
+ )
+ ax1.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax1.set_title("Kinetic Energy Components")
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Energy (J)")
+
+ ax1.legend()
+ ax1.grid()
+
+ ax2 = plt.subplot(412)
+ ax2.plot(
+ flight.totalEnergy[:, 0], flight.totalEnergy[:, 1], label="Total Energy"
+ )
+ ax2.plot(
+ flight.kineticEnergy[:, 0],
+ flight.kineticEnergy[:, 1],
+ label="Kinetic Energy",
+ )
+ ax2.plot(
+ flight.potentialEnergy[:, 0],
+ flight.potentialEnergy[:, 1],
+ label="Potential Energy",
+ )
+ ax2.set_xlim(
+ 0, flight.apogeeTime if flight.apogeeTime != 0.0 else flight.tFinal
+ )
+ ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax2.set_title("Total Mechanical Energy Components")
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Energy (J)")
+ ax2.legend()
+ ax2.grid()
+
+ ax3 = plt.subplot(413)
+ ax3.plot(
+ flight.thrustPower[:, 0],
+ flight.thrustPower[:, 1],
+ label="|Thrust Power|",
+ )
+ ax3.set_xlim(0, flight.rocket.motor.burnOutTime)
+ ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax3.set_title("Thrust Absolute Power")
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel("Power (W)")
+ ax3.legend()
+ ax3.grid()
+
+ ax4 = plt.subplot(414)
+ ax4.plot(
+ flight.dragPower[:, 0], -flight.dragPower[:, 1], label="|Drag Power|"
+ )
+ ax4.set_xlim(
+ 0, flight.apogeeTime if flight.apogeeTime != 0.0 else flight.tFinal
+ )
+ ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax4.set_title("Drag Absolute Power")
+ ax4.set_xlabel("Time (s)")
+ ax4.set_ylabel("Power (W)")
+ ax4.legend()
+ ax4.grid()
+
+ plt.subplots_adjust(hspace=1)
+ plt.show()
+
+ return None
+
+ def plotFluidMechanicsData(self):
+ """Prints out a summary of the Fluid Mechanics graphs available about
+ the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get index of out of rail time
+ outOfRailTimeIndexes = np.nonzero(flight.x[:, 0] == flight.outOfRailTime)
+ outOfRailTimeIndex = (
+ -1 if len(outOfRailTimeIndexes) == 0 else outOfRailTimeIndexes[0][0]
+ )
+
+ # Trajectory Fluid Mechanics Plots
+ fig10 = plt.figure(figsize=(9, 12))
+ fig10.suptitle(
+ "Fluid Mechanics Components of Flight: {}".format(
+ self.names_list[index]
+ )
+ )
+
+ ax1 = plt.subplot(411)
+ ax1.plot(flight.MachNumber[:, 0], flight.MachNumber[:, 1])
+ ax1.set_xlim(0, flight.tFinal)
+ ax1.set_title("Mach Number")
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Mach Number")
+ ax1.grid()
+
+ ax2 = plt.subplot(412)
+ ax2.plot(flight.ReynoldsNumber[:, 0], flight.ReynoldsNumber[:, 1])
+ ax2.set_xlim(0, flight.tFinal)
+ ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax2.set_title("Reynolds Number")
+ ax2.set_xlabel("Time (s)")
+ ax2.set_ylabel("Reynolds Number")
+ ax2.grid()
+
+ ax3 = plt.subplot(413)
+ ax3.plot(
+ flight.dynamicPressure[:, 0],
+ flight.dynamicPressure[:, 1],
+ label="Dynamic Pressure",
+ )
+ ax3.plot(
+ flight.totalPressure[:, 0],
+ flight.totalPressure[:, 1],
+ label="Total Pressure",
+ )
+ ax3.plot(
+ flight.pressure[:, 0], flight.pressure[:, 1], label="Static Pressure"
+ )
+ ax3.set_xlim(0, flight.tFinal)
+ ax3.legend()
+ ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
+ ax3.set_title("Total and Dynamic Pressure")
+ ax3.set_xlabel("Time (s)")
+ ax3.set_ylabel("Pressure (Pa)")
+ ax3.grid()
+
+ ax4 = plt.subplot(414)
+ ax4.plot(flight.angleOfAttack[:, 0], flight.angleOfAttack[:, 1])
+ # Make sure bottom and top limits are different
+ if flight.outOfRailTime * flight.angleOfAttack(flight.outOfRailTime) != 0:
+ ax4.set_xlim(flight.outOfRailTime, 10 * flight.outOfRailTime + 1)
+ ax4.set_ylim(0, flight.angleOfAttack(flight.outOfRailTime))
+ ax4.set_title("Angle of Attack")
+ ax4.set_xlabel("Time (s)")
+ ax4.set_ylabel("Angle of Attack (°)")
+ ax4.grid()
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotStabilityAndControlData(self):
+ """Prints out Rocket Stability and Control parameters graphs available
+ about the Flight
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ print(
+ "Stability And Control Data of Flight: ".format(self.names_list[index])
+ )
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ fig9 = plt.figure(figsize=(9, 6))
+ fig9.suptitle(
+ "Stability and Control Components of Flight: {}".format(
+ self.names_list[index]
+ )
+ )
+
+ ax1 = plt.subplot(211)
+ ax1.plot(flight.staticMargin[:, 0], flight.staticMargin[:, 1])
+ ax1.set_xlim(0, flight.staticMargin[:, 0][-1])
+ ax1.set_title("Static Margin")
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Static Margin (c)")
+ ax1.grid()
+
+ ax2 = plt.subplot(212)
+ maxAttitude = max(flight.attitudeFrequencyResponse[:, 1])
+ maxAttitude = maxAttitude if maxAttitude != 0 else 1
+ ax2.plot(
+ flight.attitudeFrequencyResponse[:, 0],
+ flight.attitudeFrequencyResponse[:, 1] / maxAttitude,
+ label="Attitude Angle",
+ )
+ maxOmega1 = max(flight.omega1FrequencyResponse[:, 1])
+ maxOmega1 = maxOmega1 if maxOmega1 != 0 else 1
+ ax2.plot(
+ flight.omega1FrequencyResponse[:, 0],
+ flight.omega1FrequencyResponse[:, 1] / maxOmega1,
+ label=r"$\omega_1$",
+ )
+ maxOmega2 = max(flight.omega2FrequencyResponse[:, 1])
+ maxOmega2 = maxOmega2 if maxOmega2 != 0 else 1
+ ax2.plot(
+ flight.omega2FrequencyResponse[:, 0],
+ flight.omega2FrequencyResponse[:, 1] / maxOmega2,
+ label=r"$\omega_2$",
+ )
+ maxOmega3 = max(flight.omega3FrequencyResponse[:, 1])
+ maxOmega3 = maxOmega3 if maxOmega3 != 0 else 1
+ ax2.plot(
+ flight.omega3FrequencyResponse[:, 0],
+ flight.omega3FrequencyResponse[:, 1] / maxOmega3,
+ label=r"$\omega_3$",
+ )
+ ax2.set_title("Frequency Response")
+ ax2.set_xlabel("Frequency (Hz)")
+ ax2.set_ylabel("Amplitude Magnitude Normalized")
+ ax2.set_xlim(0, 5)
+ ax2.legend()
+ ax2.grid()
+
+ plt.subplots_adjust(hspace=0.5)
+ plt.show()
+
+ return None
+
+ def plotPressureSignals(self):
+ """Prints out all Parachute Trigger Pressure Signals.
+ This function can be called also for plot pressure data for flights
+ without Parachutes, in this case the Pressure Signals will be simply
+ the pressure provided by the atmosphericModel, at Flight z positions.
+ This means that no noise will be considered if at least one parachute
+ has not been added.
+
+ This function aims to help the engineer to visually check if there
+ are anomalies with the Flight Simulation.
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ # Post-process results
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ if len(flight.rocket.parachutes) == 0:
+ plt.figure()
+ ax1 = plt.subplot(111)
+ ax1.plot(flight.z[:, 0], flight.env.pressure(flight.z[:, 1]))
+ ax1.set_title(
+ "Pressure at Rocket's Altitude, Flight: {}".format(
+ self.names_list[index]
+ )
+ )
+ ax1.set_xlabel("Time (s)")
+ ax1.set_ylabel("Pressure (Pa)")
+ ax1.set_xlim(0, flight.tFinal)
+ ax1.grid()
+
+ plt.show()
+
+ else:
+ for parachute in flight.rocket.parachutes:
+ print("Parachute: ", parachute.name)
+ parachute.noiseSignalFunction()
+ parachute.noisyPressureSignalFunction()
+ parachute.cleanPressureSignalFunction()
+
+ return None
+
+ # Start definition of 'compare' plots methods
+
+ def comparePositions(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Rocket Position Comparison", fontsize=16, y=1.02, x=0.5)
+
+ ax1 = plt.subplot(312)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.x[:, 0],
+ flight.x[:, 1],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.x.getOutputs()[0], flight.x.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.x.getInputs()[0])
+ ax1.set_ylabel(flight.x.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(313)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.y[:, 0],
+ flight.y[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.y.getOutputs()[0], flight.y.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.y.getInputs()[0])
+ ax2.set_ylabel(flight.y.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(311)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.z[:, 0],
+ flight.z[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.z.getOutputs()[0], flight.z.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.z.getInputs()[0])
+ ax3.set_ylabel(flight.z.getInputs()[0])
+ ax3.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 0.995),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareVelocities(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Rocket Velocity Comparison", fontsize=16, y=1.02, x=0.5)
+
+ ax1 = plt.subplot(412)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.vx[:, 0],
+ flight.vx[:, 1],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.vx.getOutputs()[0], flight.vx.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.vx.getInputs()[0])
+ ax1.set_ylabel(flight.vx.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(413)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.vy[:, 0],
+ flight.vy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.vy.getOutputs()[0], flight.vy.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.vy.getInputs()[0])
+ ax2.set_ylabel(flight.vy.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(414)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.vz[:, 0],
+ flight.vz[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.vz.getOutputs()[0], flight.vz.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.vz.getInputs()[0])
+ ax3.set_ylabel(flight.vz.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(411)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.speed[:, 0],
+ flight.speed[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(flight.speed.getOutputs()[0], flight.speed.getInputs()[0])
+ )
+ ax4.set_xlabel(flight.speed.getInputs()[0])
+ ax4.set_ylabel(flight.speed.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(
+ self.names_list
+ ), # TODO: Need to be more flexible here, changing the number of rows as well
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 0.995),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareStreamVelocities(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle(
+ "Rocket Freestream Velocity Comparison", fontsize=16, y=1.02, x=0.5
+ )
+
+ ax1 = plt.subplot(411)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.freestreamSpeed[:, 0],
+ flight.freestreamSpeed[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.freestreamSpeed.getOutputs()[0],
+ flight.freestreamSpeed.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.freestreamSpeed.getInputs()[0])
+ ax1.set_ylabel(flight.freestreamSpeed.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(412)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.streamVelocityX[:, 0],
+ flight.streamVelocityX[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.streamVelocityX.getOutputs()[0],
+ flight.streamVelocityX.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.streamVelocityX.getInputs()[0])
+ ax2.set_ylabel(flight.streamVelocityX.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(413)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.streamVelocityY[:, 0],
+ flight.streamVelocityY[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(
+ flight.streamVelocityY.getOutputs()[0],
+ flight.streamVelocityY.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.streamVelocityY.getInputs()[0])
+ ax3.set_ylabel(flight.streamVelocityY.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(414)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.streamVelocityZ[:, 0],
+ flight.streamVelocityZ[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(
+ flight.streamVelocityZ.getOutputs()[0],
+ flight.streamVelocityZ.getInputs()[0],
+ )
+ )
+ ax4.set_xlabel(flight.streamVelocityZ.getInputs()[0])
+ ax4.set_ylabel(flight.streamVelocityZ.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 0.995),
+ )
+ # TODO: Create a option to insert watermark or not, including RocketPy logo
+ # fig.text(
+ # x=0.8,
+ # y=0,
+ # s="created with RocketPy",
+ # fontsize=10,
+ # color="black",
+ # alpha=1,
+ # ha="center",
+ # va="center",
+ # rotation=0,
+ # )
+ fig.tight_layout()
+ return None
+
+ def compareAccelerations(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Rocket Acceleration Comparison", fontsize=16, y=1.02, x=0.5)
+
+ ax1 = plt.subplot(412)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.ax[:, 0],
+ flight.ax[:, 1],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.ax.getOutputs()[0], flight.ax.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.ax.getInputs()[0])
+ ax1.set_ylabel(flight.ax.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(413)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.vy[:, 0],
+ flight.vy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.ay.getOutputs()[0], flight.ay.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.ay.getInputs()[0])
+ ax2.set_ylabel(flight.ay.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(414)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.vz[:, 0],
+ flight.vz[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.az.getOutputs()[0], flight.az.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.vz.getInputs()[0])
+ ax3.set_ylabel(flight.vz.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(411)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.acceleration[:, 0],
+ flight.acceleration[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(
+ flight.acceleration.getOutputs()[0], flight.acceleration.getInputs()[0]
+ )
+ )
+ ax4.set_xlabel(flight.acceleration.getInputs()[0])
+ ax4.set_ylabel(flight.acceleration.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 0.995),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareEulerAngles(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Euler Angles Comparison", fontsize=16, y=1.02, x=0.5)
+
+ ax1 = plt.subplot(311)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.phi[:, 0],
+ flight.phi[:, 1],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.phi.getOutputs()[0], flight.phi.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.phi.getInputs()[0])
+ ax1.set_ylabel(flight.phi.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(312)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.theta[:, 0],
+ flight.theta[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.theta.getOutputs()[0], flight.theta.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.theta.getInputs()[0])
+ ax2.set_ylabel(flight.theta.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(313)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.psi[:, 0],
+ flight.psi[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.psi.getOutputs()[0], flight.psi.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.psi.getInputs()[0])
+ ax3.set_ylabel(flight.psi.getOutputs()[0])
+ ax3.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 0.995),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareQuaternions(self):
+
+ fig = plt.figure(figsize=(10, 20 / 3)) # width, height
+ fig.suptitle("Quaternions Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(221)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.e0[:, 0],
+ flight.e0[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.e0.getOutputs()[0], flight.e0.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.e0.getInputs()[0])
+ ax1.set_ylabel(flight.e0.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(222)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.e1[:, 0],
+ flight.e1[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.e1.getOutputs()[0], flight.e1.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.e1.getInputs()[0])
+ ax2.set_ylabel(flight.e1.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(223)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.e2[:, 0],
+ flight.e2[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.e2.getOutputs()[0], flight.e2.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.e2.getInputs()[0])
+ ax3.set_ylabel(flight.e2.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(224)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.e3[:, 0],
+ flight.e3[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(flight.e3.getOutputs()[0], flight.e3.getInputs()[0])
+ )
+ ax4.set_xlabel(flight.e3.getInputs()[0])
+ ax4.set_ylabel(flight.e3.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAttitudeAngles(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Attitude Angles Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(311)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.pathAngle[:, 0],
+ flight.pathAngle[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.pathAngle.getOutputs()[0], flight.pathAngle.getInputs()[0]
+ )
+ )
+ ax1.set_xlabel(flight.pathAngle.getInputs()[0])
+ ax1.set_ylabel(flight.pathAngle.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(312)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.attitudeAngle[:, 0],
+ flight.attitudeAngle[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.attitudeAngle.getOutputs()[0],
+ flight.attitudeAngle.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.attitudeAngle.getInputs()[0])
+ ax2.set_ylabel(flight.attitudeAngle.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(313)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.lateralAttitudeAngle[:, 0],
+ flight.lateralAttitudeAngle[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(
+ flight.lateralAttitudeAngle.getOutputs()[0],
+ flight.lateralAttitudeAngle.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.lateralAttitudeAngle.getInputs()[0])
+ ax3.set_ylabel(flight.lateralAttitudeAngle.getOutputs()[0])
+ ax3.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAngularVelocities(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Angular Velocities Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(311)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.w1[:, 0],
+ flight.w1[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(flight.w1.getOutputs()[0], flight.w1.getInputs()[0])
+ )
+ ax1.set_xlabel(flight.w1.getInputs()[0])
+ ax1.set_ylabel(flight.w1.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(312)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.w2[:, 0],
+ flight.w2[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(flight.w2.getOutputs()[0], flight.w2.getInputs()[0])
+ )
+ ax2.set_xlabel(flight.w2.getInputs()[0])
+ ax2.set_ylabel(flight.w2.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(313)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.w3[:, 0],
+ flight.w3[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(flight.w3.getOutputs()[0], flight.w3.getInputs()[0])
+ )
+ ax3.set_xlabel(flight.w3.getInputs()[0])
+ ax3.set_ylabel(flight.w3.getOutputs()[0])
+ ax3.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAngularAccelerations(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10)) # width, height
+ fig.suptitle("Angular Accelerations Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(311)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.alpha1[:, 0],
+ flight.alpha1[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.alpha1.getOutputs()[0], flight.alpha1.getInputs()[0]
+ )
+ )
+ ax1.set_xlabel(flight.alpha1.getInputs()[0])
+ ax1.set_ylabel(flight.alpha1.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(312)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.alpha2[:, 0],
+ flight.alpha2[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.alpha2.getOutputs()[0], flight.alpha2.getInputs()[0]
+ )
+ )
+ ax2.set_xlabel(flight.alpha2.getInputs()[0])
+ ax2.set_ylabel(flight.alpha2.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(313)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.alpha3[:, 0],
+ flight.alpha3[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(
+ flight.alpha3.getOutputs()[0], flight.alpha3.getInputs()[0]
+ )
+ )
+ ax3.set_xlabel(flight.alpha3.getInputs()[0])
+ ax3.set_ylabel(flight.alpha3.getOutputs()[0])
+ ax3.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAerodynamicForces(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 20 / 3)) # width, height
+ fig.suptitle("Aerodynamic Forces Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(211)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.aerodynamicDrag[:, 0],
+ flight.aerodynamicDrag[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.aerodynamicDrag.getOutputs()[0],
+ flight.aerodynamicDrag.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.aerodynamicDrag.getInputs()[0])
+ ax1.set_ylabel(flight.aerodynamicDrag.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(212)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.aerodynamicLift[:, 0],
+ flight.aerodynamicLift[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.aerodynamicLift.getOutputs()[0],
+ flight.aerodynamicLift.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.aerodynamicLift.getInputs()[0])
+ ax2.set_ylabel(flight.aerodynamicLift.getOutputs()[0])
+ ax2.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAerodynamicMoments(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+ fig = plt.figure(figsize=(7, 20 / 3)) # width, height
+ fig.suptitle("Aerodynamic Moments Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(211)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.aerodynamicBendingMoment[:, 0],
+ flight.aerodynamicBendingMoment[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.aerodynamicBendingMoment.getOutputs()[0],
+ flight.aerodynamicBendingMoment.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.aerodynamicBendingMoment.getInputs()[0])
+ ax1.set_ylabel(flight.aerodynamicBendingMoment.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(212)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.aerodynamicSpinMoment[:, 0],
+ flight.aerodynamicSpinMoment[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.aerodynamicSpinMoment.getOutputs()[0],
+ flight.aerodynamicSpinMoment.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.aerodynamicSpinMoment.getInputs()[0])
+ ax2.set_ylabel(flight.aerodynamicSpinMoment.getOutputs()[0])
+ ax2.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareEnergies(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 50 / 3)) # width, height
+ fig.suptitle("Energies Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(511)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.kineticEnergy[:, 0],
+ flight.kineticEnergy[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.kineticEnergy.getOutputs()[0],
+ flight.kineticEnergy.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.kineticEnergy.getInputs()[0])
+ ax1.set_ylabel(flight.kineticEnergy.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(512)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.rotationalEnergy[:, 0],
+ flight.rotationalEnergy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.rotationalEnergy.getOutputs()[0],
+ flight.rotationalEnergy.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.rotationalEnergy.getInputs()[0])
+ ax2.set_ylabel(flight.rotationalEnergy.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(513)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.translationalEnergy[:, 0],
+ flight.translationalEnergy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(
+ flight.translationalEnergy.getOutputs()[0],
+ flight.translationalEnergy.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.translationalEnergy.getInputs()[0])
+ ax3.set_ylabel(flight.translationalEnergy.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(514)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.potentialEnergy[:, 0],
+ flight.potentialEnergy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(
+ flight.potentialEnergy.getOutputs()[0],
+ flight.potentialEnergy.getInputs()[0],
+ )
+ )
+ ax4.set_xlabel(flight.potentialEnergy.getInputs()[0])
+ ax4.set_ylabel(flight.potentialEnergy.getOutputs()[0])
+ ax4.grid(True)
+
+ ax5 = plt.subplot(515)
+ for index, flight in enumerate(self.trajectory_list):
+ ax5.plot(
+ flight.totalEnergy[:, 0],
+ flight.totalEnergy[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax5.set_xlim(0, max_time)
+ ax5.set_title(
+ "{} x {}".format(
+ flight.totalEnergy.getOutputs()[0],
+ flight.totalEnergy.getInputs()[0],
+ )
+ )
+ ax5.set_xlabel(flight.totalEnergy.getInputs()[0])
+ ax5.set_ylabel(flight.totalEnergy.getOutputs()[0])
+ ax5.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def comparePowers(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 20 / 3)) # width, height
+ fig.suptitle("Powers Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(211)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.thrustPower[:, 0],
+ flight.thrustPower[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.thrustPower.getOutputs()[0],
+ flight.thrustPower.getInputs()[0],
+ )
+ )
+
+ ax1.set_xlabel(flight.thrustPower.getInputs()[0])
+ ax1.set_ylabel(flight.thrustPower.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(212)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.dragPower[:, 0],
+ flight.dragPower[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.dragPower.getOutputs()[0],
+ flight.dragPower.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.dragPower.getInputs()[0])
+ ax2.set_ylabel(flight.dragPower.getOutputs()[0])
+ ax2.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareRailButtonsForces(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+ fig = plt.figure(figsize=(10, 20 / 3)) # width, height
+ fig.suptitle("Rail Buttons Forces Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(221)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.rocket.railButtons is None:
+ continue
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.railButton1NormalForce[:, 0],
+ flight.railButton1NormalForce[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+
+ ax1.set_title(
+ "{} x {}".format(
+ flight.railButton1NormalForce.getOutputs()[0],
+ flight.railButton1NormalForce.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.railButton1NormalForce.getInputs()[0])
+ ax1.set_ylabel(flight.railButton1NormalForce.getOutputs()[0])
+ ax1.set_xlim(0, max_time)
+ ax1.grid(True)
+
+ ax2 = plt.subplot(223)
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.rocket.railButtons is None:
+ continue
+ ax2.plot(
+ flight.railButton2NormalForce[:, 0],
+ flight.railButton2NormalForce[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_title(
+ "{} x {}".format(
+ flight.railButton2NormalForce.getOutputs()[0],
+ flight.railButton2NormalForce.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.railButton2NormalForce.getInputs()[0])
+ ax2.set_ylabel(flight.railButton2NormalForce.getOutputs()[0])
+ ax2.set_xlim(0, max_time)
+ ax2.grid(True)
+
+ ax3 = plt.subplot(222)
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.rocket.railButtons is None:
+ continue
+ ax3.plot(
+ flight.railButton1ShearForce[:, 0],
+ flight.railButton1ShearForce[:, 1],
+ # color=self.colors_scale[index],
+ )
+
+ ax3.set_title(
+ "{} x {}".format(
+ flight.railButton1ShearForce.getOutputs()[0],
+ flight.railButton1ShearForce.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.railButton1ShearForce.getInputs()[0])
+ ax3.set_ylabel(flight.railButton1ShearForce.getOutputs()[0])
+ ax3.set_xlim(0, max_time)
+ ax3.grid(True)
+
+ ax4 = plt.subplot(224)
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.rocket.railButtons is None:
+ continue
+ ax4.plot(
+ flight.railButton2ShearForce[:, 0],
+ flight.railButton2ShearForce[:, 1],
+ # color=self.colors_scale[index],
+ )
+
+ ax4.set_title(
+ "{} x {}".format(
+ flight.railButton2ShearForce.getOutputs()[0],
+ flight.railButton2ShearForce.getInputs()[0],
+ )
+ )
+ ax4.set_xlabel(flight.railButton2ShearForce.getInputs()[0])
+ ax4.set_ylabel(flight.railButton2ShearForce.getOutputs()[0])
+ ax4.set_xlim(0, max_time)
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAnglesOfAttack(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10 / 3)) # width, height
+ fig.suptitle("Angles of Attack Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(111)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.angleOfAttack[:, 0],
+ flight.angleOfAttack[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.angleOfAttack.getOutputs()[0],
+ flight.angleOfAttack.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.angleOfAttack.getInputs()[0])
+ ax1.set_ylabel(flight.angleOfAttack.getOutputs()[0])
+ ax1.grid(True)
+
+ # TODO: Maybe simplify this code with the use of a function to add legend
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ # TODO: Static Margin is not working properly, we need to understand why!
+ def compareStaticMargins(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(7, 10 / 3)) # width, height
+ fig.suptitle("Static Margins Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(111)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.staticMargin[:, 0],
+ flight.staticMargin[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.staticMargin.getOutputs()[0],
+ flight.staticMargin.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.staticMargin.getInputs()[0])
+ ax1.set_ylabel(flight.staticMargin.getOutputs()[0])
+ ax1.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareFluidMechanics(self):
+ """_summary_
+
+ Returns
+ -------
+ None
+ """
+ fig = plt.figure(figsize=(10, 20 / 3)) # width, height
+ fig.suptitle("Fluid Mechanics Comparison", fontsize=16, y=1.06, x=0.5)
+
+ ax1 = plt.subplot(221)
+ max_time = 0
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.MachNumber[:, 0],
+ flight.MachNumber[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ max_time = flight.tFinal if flight.tFinal > max_time else max_time
+ ax1.set_xlim(0, max_time)
+ ax1.set_title(
+ "{} x {}".format(
+ flight.MachNumber.getOutputs()[0], flight.MachNumber.getInputs()[0]
+ )
+ )
+ ax1.set_xlabel(flight.MachNumber.getInputs()[0])
+ ax1.set_ylabel(flight.MachNumber.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(222)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.ReynoldsNumber[:, 0],
+ flight.ReynoldsNumber[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_xlim(0, max_time)
+ ax2.set_title(
+ "{} x {}".format(
+ flight.ReynoldsNumber.getOutputs()[0],
+ flight.ReynoldsNumber.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.ReynoldsNumber.getInputs()[0])
+ ax2.set_ylabel(flight.ReynoldsNumber.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(223)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.dynamicPressure[:, 0],
+ flight.dynamicPressure[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_xlim(0, max_time)
+ ax3.set_title(
+ "{} x {}".format(
+ flight.dynamicPressure.getOutputs()[0],
+ flight.dynamicPressure.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.dynamicPressure.getInputs()[0])
+ ax3.set_ylabel(flight.dynamicPressure.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(224)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.totalPressure[:, 0],
+ flight.totalPressure[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax4.set_xlim(0, max_time)
+ ax4.set_title(
+ "{} x {}".format(
+ flight.totalPressure.getOutputs()[0],
+ flight.totalPressure.getInputs()[0],
+ )
+ )
+ ax4.set_xlabel(flight.totalPressure.getInputs()[0])
+ ax4.set_ylabel(flight.totalPressure.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+ fig.tight_layout()
+
+ return None
+
+ def compareAttitudeFrequencyResponses(self):
+ """_summary_
+
+ Returns
+ -------
+ _type_
+ _description_
+ """
+ fig = plt.figure(figsize=(10, 20 / 3)) # width, height
+ fig.suptitle(
+ "Attitude Frequency Responses Comparison", fontsize=16, y=1.06, x=0.5
+ )
+
+ ax1 = plt.subplot(221)
+ for index, flight in enumerate(self.trajectory_list):
+ if flight.postProcessed is False:
+ flight.postProcess()
+ ax1.plot(
+ flight.attitudeFrequencyResponse[:, 0],
+ flight.attitudeFrequencyResponse[:, 1],
+ label=self.names_list[index],
+ # color=self.colors_scale[index],
+ )
+ ax1.set_title(
+ "{} x {}".format(
+ flight.attitudeFrequencyResponse.getOutputs()[0],
+ flight.attitudeFrequencyResponse.getInputs()[0],
+ )
+ )
+ ax1.set_xlabel(flight.attitudeFrequencyResponse.getInputs()[0])
+ ax1.set_ylabel(flight.attitudeFrequencyResponse.getOutputs()[0])
+ ax1.grid(True)
+
+ ax2 = plt.subplot(222)
+ for index, flight in enumerate(self.trajectory_list):
+ ax2.plot(
+ flight.omega1FrequencyResponse[:, 0],
+ flight.omega1FrequencyResponse[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax2.set_title(
+ "{} x {}".format(
+ flight.omega1FrequencyResponse.getOutputs()[0],
+ flight.omega1FrequencyResponse.getInputs()[0],
+ )
+ )
+ ax2.set_xlabel(flight.omega1FrequencyResponse.getInputs()[0])
+ ax2.set_ylabel(flight.omega1FrequencyResponse.getOutputs()[0])
+ ax2.grid(True)
+
+ ax3 = plt.subplot(223)
+ for index, flight in enumerate(self.trajectory_list):
+ ax3.plot(
+ flight.omega2FrequencyResponse[:, 0],
+ flight.omega2FrequencyResponse[:, 1],
+ # color=self.colors_scale[index],
+ )
+ ax3.set_title(
+ "{} x {}".format(
+ flight.omega2FrequencyResponse.getOutputs()[0],
+ flight.omega2FrequencyResponse.getInputs()[0],
+ )
+ )
+ ax3.set_xlabel(flight.omega2FrequencyResponse.getInputs()[0])
+ ax3.set_ylabel(flight.omega2FrequencyResponse.getOutputs()[0])
+ ax3.grid(True)
+
+ ax4 = plt.subplot(224)
+ for index, flight in enumerate(self.trajectory_list):
+ ax4.plot(
+ flight.omega3FrequencyResponse[:, 0],
+ flight.omega3FrequencyResponse[:, 1],
+ # color=self.colors_scale[index],
+ )
+
+ ax4.set_title(
+ "{} x {}".format(
+ flight.omega3FrequencyResponse.getOutputs()[0],
+ flight.omega3FrequencyResponse.getInputs()[0],
+ )
+ )
+ ax4.set_xlabel(flight.omega3FrequencyResponse.getInputs()[0])
+ ax4.set_ylabel(flight.omega3FrequencyResponse.getOutputs()[0])
+ ax4.grid(True)
+
+ fig.legend(
+ loc="upper center",
+ ncol=len(self.names_list),
+ fancybox=True,
+ shadow=True,
+ fontsize=10,
+ bbox_to_anchor=(0.5, 1),
+ )
+
+ fig.tight_layout()
+
+ return None
+
+ def comparePressureSignals(self):
+ """_summary_"""
+ print("Still not implemented")
+ pass
+
+ def compareFinFlutterAnalysis(self):
+ # Should only work if the fin flutter analysis was ran before. # TODO: Add boolean!
+ print("Still not implemented yet!")
+ return None
+
+ @staticmethod
+ def compareTrajectories3D(trajectory_list, names_list, legend=None):
+ """Creates a trajectory plot combining the trajectories listed.
+ This function was created based two source-codes:
+ - Mateus Stano: https://github.com/RocketPy-Team/Hackathon_2020/pull/123
+ - Dyllon Preston: https://github.com/Dyllon-P/MBS-Template/blob/main/MBS.py
+ Also, some of the credits go to Georgia Tech Experimental Rocketry Club (GTXR)
+ as well.
+ The final function was created by the RocketPy Team.
+
+ Parameters
+ ----------
+ trajectory_list : list, array
+ List of trajectories. Must be in the form of [trajectory_1, trajectory_2, ..., trajectory_n]
+ where each element is a list with the arrays regarding positions in x, y and z [x, y, z].
+ The trajectories must be in the same reference frame. The z coordinate must be referenced
+ to the ground or to the sea level, but it is important that all trajectories are passed
+ in the same reference.
+ names_list : list, optional
+ List of strings with the name of each trajectory inputted. The names must be in
+ the same order as the trajectories in trajectory_list. If no names are passed, the
+ trajectories will be named as "Trajectory 1", "Trajectory 2", ..., "Trajectory n".
+ legend : boolean, optional
+ Whether legend will or will not be plotted. Default is True
+
+ Returns
+ -------
+ None
+ """
+
+ # TODO: Allow the user to set the colors or color style
+ # TODO: Allow the user to set the line style
+
+ # Initialize variables
+ maxX, maxY, maxZ, minX, minY, minZ, maxXY, minXY = 0, 0, 0, 0, 0, 0, 0, 0
+
+ # Create the figure
+ fig1 = plt.figure(figsize=(7, 7))
+ fig1.suptitle("Flight Trajectories Comparison", fontsize=16, y=0.95, x=0.5)
+ ax1 = plt.subplot(
+ 111,
+ projection="3d",
+ )
+
+ # Iterate through trajectories
+ for index, flight in enumerate(trajectory_list):
+
+ x, y, z = flight
+
+ # Find max/min values for each component
+ maxX = max(x) if max(x) > maxX else maxX
+ maxY = max(y) if max(y) > maxY else maxY
+ maxZ = max(z) if max(z) > maxZ else maxZ
+ minX = min(x) if min(x) < minX else minX
+ minY = min(x) if min(x) < minX else minX
+ minZ = min(z) if min(z) < minZ else minZ
+ maxXY = max(maxX, maxY) if max(maxX, maxY) > maxXY else maxXY
+ minXY = min(minX, minY) if min(minX, minY) > minXY else minXY
+
+ # Add Trajectory as a plot in main figure
+ ax1.plot(x, y, z, linewidth="2", label=names_list[index])
+
+ # Plot settings
+ # TODO: Don't know why, but tha z_label is not working properly
+ ax1.scatter(0, 0, 0, color="black", s=10, marker="o")
+ ax1.set_xlabel("X - East (m)")
+ ax1.set_ylabel("Y - North (m)")
+ ax1.set_zlabel("Z - Altitude (m)")
+ ax1.set_zlim3d([minZ, maxZ])
+ ax1.set_ylim3d([minXY, maxXY])
+ ax1.set_xlim3d([minXY, maxXY])
+ ax1.view_init(15, 45)
+
+ # Add legend
+ if legend:
+ fig1.legend()
+
+ fig1.tight_layout()
+
+ return None
+
+ def compareFlightTrajectories3D(self, legend=None, savefig=None):
+ """Creates a trajectory plot that is the combination of the trajectories of
+ the Flight objects passed via a Python list.
+
+ Parameters
+ ----------
+ legend : boolean, optional
+ Whether legend will or will not be included. Default is True
+ savefig : string, optional
+ If a string is passed, the figure will be saved in the path passed.
+
+ Returns
+ -------
+ None
+
+ """
+
+ # Iterate through Flight objects and create a list of trajectories
+ trajectory_list = []
+ for index, flight in enumerate(self.trajectory_list):
+
+ # Check post process
+ if flight.postProcessed is False:
+ flight.postProcess()
+
+ # Get trajectories
+ x = flight.x[:, 1]
+ y = flight.y[:, 1]
+ z = flight.z[:, 1] - flight.env.elevation
+ trajectory_list.append([x, y, z])
+
+ # Call compareTrajectories3D function to do the hard work
+ self.compareTrajectories3D(trajectory_list, self.names_list, legend=legend)
+
+ return None
+
+ def compareFlightTrajectories2D(self, legend=None):
+ """...
+ Let it chose the two planes...
+ - XY projection plot
+ - XZ projection plot
+ - YZ projection plot
+ """
+ print("Still not implemented yet!")
+ pass
+
+ @staticmethod
+ def compareFlightSimulators():
+ """Allow the user to compare a flight from RocketPy (or more than one)
+ against a flight from another simulator (e.g. OpenRocket, Cambridge, etc.)
+ Still not implemented yet.
+ Should also allow comparison between RocketPy and actual flight data.
+ """
+ print("Still not implemented yet!")
+ pass
+
+ # Start definition of animations methods
+
+ def animate(self, start=0, stop=None, fps=12, speed=4, elev=None, azim=None):
+ """Plays an animation of the flight. Not implemented yet. Only
+ kinda works outside notebook.
+ """
+ for index, flight in enumerate(self.trajectory_list):
+ # Set up stopping time
+ stop = flight.tFinal if stop is None else stop
+ # Speed = 4 makes it almost real time - matplotlib is way to slow
+ # Set up graph
+ fig = plt.figure(figsize=(18, 15))
+ fig.suptitle("Flight: {}".format(self.names_list[index]))
+ axes = fig.gca(projection="3d")
+ # Initialize time
+ timeRange = np.linspace(start, stop, fps * (stop - start))
+ # Initialize first frame
+ axes.set_title("Trajectory and Velocity Animation")
+ axes.set_xlabel("X (m)")
+ axes.set_ylabel("Y (m)")
+ axes.set_zlabel("Z (m)")
+ axes.view_init(elev, azim)
+ R = axes.quiver(0, 0, 0, 0, 0, 0, color="r", label="Rocket")
+ V = axes.quiver(0, 0, 0, 0, 0, 0, color="g", label="Velocity")
+ W = axes.quiver(0, 0, 0, 0, 0, 0, color="b", label="Wind")
+ S = axes.quiver(0, 0, 0, 0, 0, 0, color="black", label="Freestream")
+ axes.legend()
+ # Animate
+ for t in timeRange:
+ R.remove()
+ V.remove()
+ W.remove()
+ S.remove()
+ # Calculate rocket position
+ Rx, Ry, Rz = flight.x(t), flight.y(t), flight.z(t)
+ Ru = 1 * (
+ 2 * (flight.e1(t) * flight.e3(t) + flight.e0(t) * flight.e2(t))
+ )
+ Rv = 1 * (
+ 2 * (flight.e2(t) * flight.e3(t) - flight.e0(t) * flight.e1(t))
+ )
+ Rw = 1 * (1 - 2 * (flight.e1(t) ** 2 + flight.e2(t) ** 2))
+ # Calculate rocket Mach number
+ Vx = flight.vx(t) / 340.40
+ Vy = flight.vy(t) / 340.40
+ Vz = flight.vz(t) / 340.40
+ # Calculate wind Mach Number
+ z = flight.z(t)
+ Wx = flight.env.windVelocityX(z) / 20
+ Wy = flight.env.windVelocityY(z) / 20
+ # Calculate freestream Mach Number
+ Sx = flight.streamVelocityX(t) / 340.40
+ Sy = flight.streamVelocityY(t) / 340.40
+ Sz = flight.streamVelocityZ(t) / 340.40
+ # Plot Quivers
+ R = axes.quiver(Rx, Ry, Rz, Ru, Rv, Rw, color="r")
+ V = axes.quiver(Rx, Ry, Rz, -Vx, -Vy, -Vz, color="g")
+ W = axes.quiver(Rx - Vx, Ry - Vy, Rz - Vz, Wx, Wy, 0, color="b")
+ S = axes.quiver(Rx, Ry, Rz, Sx, Sy, Sz, color="black")
+ # Adjust axis
+ axes.set_xlim(Rx - 1, Rx + 1)
+ axes.set_ylim(Ry - 1, Ry + 1)
+ axes.set_zlim(Rz - 1, Rz + 1)
+ # plt.pause(1/(fps*speed))
+ try:
+ plt.pause(1 / (fps * speed))
+ except:
+ time.sleep(1 / (fps * speed))
+
+ def info(self):
+ """Prints out a summary of the data available about the Flight.
+
+ Parameters
+ ----------
+ None
+
+ Return
+ ------
+ None
+ """
+
+ # Print initial conditions
+ self.printInitialConditionsData()
+
+ # Print surface wind conditions
+ self.printSurfaceWindConditions()
+
+ # Print launch rail orientation
+ self.printLaunchRailConditions()
+
+ # Print out of rail conditions
+ self.printOutOfRailConditions()
+
+ # Print burnOut conditions
+ self.printBurnOutConditions()
+
+ # Print apogee conditions
+ self.printApogeeConditions()
+
+ # Print events registered
+ self.printEventsRegistered()
+
+ # Print impact conditions
+ self.printImpactConditions()
+
+ # Print maximum values
+ self.printMaximumValues()
+
+ # Print Numerical Integration Information
+ self.printNumericalIntegrationSettings()
+
+ return None
+
+ def allInfo(self, mode="basic"):
+ """Prints out all data and graphs available about the Flight.
+ It call info() and then all the plots available.
+
+ Parameters
+ ----------
+ mode : str, optional
+ The level of detail to print. The default is "basic".
+ Options are "compare" and "basic".
+ "compare" prints all data and graphs available.
+ "basic" prints will basically repeat the code inside a for loop.
+
+ Return
+ ------
+ None
+ """
+ if mode == "basic":
+
+ # Print a summary of data about the flight
+ self.info()
+
+ # Plot flight trajectory in a 3D plot
+ self.plot3dTrajectory()
+
+ # Plot
+ self.plotLinearKinematicsData()
+
+ # Plot
+ self.plotFlightPathAngleData()
+
+ # Plot
+ self.plotAttitudeData()
+
+ # Plot
+ self.plotAngularKinematicsData()
+
+ # Plot
+ self.plotTrajectoryForceData()
+
+ # Plot
+ self.plotEnergyData()
+
+ # Plot
+ self.plotFluidMechanicsData()
+
+ # Plot pressure signals recorded by the sensors
+ self.plotPressureSignals()
+
+ # Plot Stability and Control Data
+ self.plotStabilityAndControlData()
+
+ elif mode == "compare":
+
+ self.info()
+
+ self.compareFlightTrajectories3D()
+
+ self.comparePositions()
+
+ self.compareVelocities()
+
+ self.compareStreamVelocities()
+
+ self.compareAccelerations()
+
+ self.compareAngularVelocities()
+
+ self.compareAngularAccelerations()
+
+ self.compareEulerAngles()
+
+ self.compareQuaternions()
+
+ self.compareAttitudeAngles()
+
+ self.compareAnglesOfAttack()
+
+ self.compareStaticMargins()
+
+ self.compareAerodynamicForces()
+
+ self.compareAerodynamicMoments()
+
+ self.compareRailButtonsForces()
+
+ self.compareEnergies()
+
+ self.comparePowers()
+
+ self.compareFluidMechanics()
+
+ # self.comparePressureSignals()
+
+ # self.compareFinFlutterAnalysis()
+
+ self.compareAttitudeFrequencyResponses()
+
+ else:
+ raise ValueError("Mode must be 'basic' or 'compare'")
+
+ return None
diff --git a/rocketpy/utilities.py b/rocketpy/utilities.py
index 7671b414b..61073b2b9 100644
--- a/rocketpy/utilities.py
+++ b/rocketpy/utilities.py
@@ -201,155 +201,6 @@ def du(z, u):
return altitudeFunction, velocityFunction, final_sol
-def compareTrajectories(
- trajectory_list,
- names=None,
- legend=True,
-):
- """Creates a trajectory plot combining the trajectories listed.
- This function was created based two source-codes:
- - Mateus Stano: https://github.com/RocketPy-Team/Hackathon_2020/pull/123
- - Dyllon Preston: https://github.com/Dyllon-P/MBS-Template/blob/main/MBS.py
-
- Parameters
- ----------
- trajectory_list : list, array
- List of trajectories. Must be in the form of [trajectory_1, trajectory_2, ..., trajectory_n]
- where each element is a list with the arrays regarding positions in x, y and z [x, y, z].
- The trajectories must be in the same reference frame. The z coordinate must be referenced
- to the ground or to the sea level, but it is important that all trajectories are passed
- in the same reference.
- names : list, optional
- List of strings with the name of each trajectory inputted. The names must be in
- the same order as the trajectories in trajectory_list. If no names are passed, the
- trajectories will be named as "Trajectory 1", "Trajectory 2", ..., "Trajectory n".
- legend : boolean, optional
- Whether legend will or will not be plotted. Default is True
-
- Returns
- -------
- None
-
- """
- # TODO: Allow the user to catch different planes (xy, yz, xz) from the main plot (this can be done in a separate function)
- # TODO: Allow the user to set the colors or color style
- # TODO: Allow the user to set the line style
-
- # Initialize variables
- maxX, maxY, maxZ, minX, minY, minZ, maxXY, minXY = 0, 0, 0, 0, 0, 0, 0, 0
-
- names = (
- [("Trajectory " + str(i + 1)) for i in range(len(trajectory_list))]
- if names == None
- else names
- )
-
- # Create the figure
- fig1 = plt.figure(figsize=(9, 9))
- ax1 = plt.subplot(111, projection="3d")
-
- # Iterate through trajectories
- for i, trajectory in enumerate(trajectory_list):
-
- x, y, z = trajectory
-
- # Find max/min values for each component
- maxX = max(x) if max(x) > maxX else maxX
- maxY = max(y) if max(y) > maxY else maxY
- maxZ = max(z) if max(z) > maxZ else maxZ
- minX = min(x) if min(x) < minX else minX
- minY = min(x) if min(x) < minX else minX
- minZ = min(z) if min(z) < minZ else minZ
- maxXY = max(maxX, maxY) if max(maxX, maxY) > maxXY else maxXY
- minXY = min(minX, minY) if min(minX, minY) > minXY else minXY
-
- # Add Trajectory as a plot in main figure
- ax1.plot(x, y, z, linewidth="2", label=names[i])
-
- # Plot settings
- ax1.scatter(0, 0, 0)
- ax1.set_xlabel("X - East (m)")
- ax1.set_ylabel("Y - North (m)")
- ax1.set_zlabel("Z - Altitude (m)")
- ax1.set_title("Flight Trajectories Comparison")
- ax1.set_zlim3d([minZ, maxZ])
- ax1.set_ylim3d([minXY, maxXY])
- ax1.set_xlim3d([minXY, maxXY])
- ax1.view_init(15, 45)
- if legend:
- plt.legend()
- plt.show()
-
- return None
-
-
-def compareFlightTrajectories(
- flight_list,
- names=None,
- legend=True,
-):
- """Creates a trajectory plot that is the combination of the trajectories of
- the Flight objects passed via a Python list.
-
- Parameters
- ----------
- flight_list : list, array
- List of FLight objects. The flights must be in the same reference frame.
- names : list, optional
- List of strings with the name of each trajectory inputted. The names must be in
- the same order as the trajectories in flight_list
- legend : boolean, optional
- Whether legend will or will not be included. Default is True
-
- Returns
- -------
- None
-
- """
- # TODO: Allow the user to catch different planes (xy, yz, xz) from the main plot
- # TODO: Allow the user to set the colors or color style
- # TODO: Allow the user to set the line style
-
- # Iterate through Flight objects and create a list of trajectories
- trajectory_list = []
- for flight in flight_list:
-
- # Check post process
- if flight.postProcessed is False:
- flight.postProcess()
-
- # Get trajectories
- x = flight.x[:, 1]
- y = flight.y[:, 1]
- z = flight.z[:, 1] - flight.env.elevation
- trajectory_list.append([x, y, z])
-
- # Call compareTrajectories function to do the hard work
- compareTrajectories(trajectory_list, names, legend)
-
- return None
-
-
-def compareAllInfo(flight_list, names=None):
- """Creates a plot with the altitude, velocity and acceleration of the
- Flight objects passed via a Python list.
-
- Parameters
- ----------
- flight_list : list, array
- List of FLight objects. The flights must be in the same reference frame.
- names : list, optional
- List of strings with the name of each trajectory inputted. The names must be in
- the same order as the trajectories in flight_list
-
- Returns
- -------
- None
-
- """
- return None
-
-
def create_dispersion_dictionary(filename):
"""Creates a dictionary with the rocket data provided by a .csv file.
File should be organized in four columns: attribute_class, parameter_name,
diff --git a/setup.py b/setup.py
index 767c8d76b..6237e49a4 100644
--- a/setup.py
+++ b/setup.py
@@ -5,13 +5,14 @@
setuptools.setup(
name="rocketpy",
- version="0.12.0",
+ version="0.12.1",
install_requires=[
"numpy>=1.0",
"scipy>=1.0",
"matplotlib>=3.0",
- "netCDF4>=1.4",
+ "netCDF4>=1.4,<1.6",
"windrose>=1.6.8",
+ "ipywidgets>=7.6.3",
"requests",
"pytz",
"timezonefinder",
diff --git a/tests/test_flight_plots.py b/tests/test_flight_plots.py
new file mode 100644
index 000000000..8b4bda93b
--- /dev/null
+++ b/tests/test_flight_plots.py
@@ -0,0 +1,100 @@
+from unittest.mock import patch
+
+import matplotlib as plt
+import numpy as np
+import pytest
+from rocketpy import Environment, Flight, Rocket, SolidMotor
+from rocketpy.plots import flight_plots
+
+plt.rcParams.update({"figure.max_open_warning": 0})
+
+
+@patch("matplotlib.pyplot.show")
+def test_flight_plots(mock_show):
+
+ test_env = Environment(railLength=5)
+ test_env.setAtmosphericModel(type="CustomAtmosphere", wind_u=0.3, wind_v=-1)
+
+ test_motor = SolidMotor(
+ thrustSource=2000,
+ burnOut=3.9,
+ grainNumber=5,
+ grainSeparation=5 / 1000,
+ grainDensity=1815,
+ grainOuterRadius=33 / 1000,
+ grainInitialInnerRadius=15 / 1000,
+ grainInitialHeight=120 / 1000,
+ nozzleRadius=33 / 1000,
+ throatRadius=11 / 1000,
+ interpolationMethod="linear",
+ )
+
+ test_rocket = Rocket(
+ motor=test_motor,
+ radius=127 / 2000,
+ mass=19.197 - 2.956,
+ inertiaI=6.60,
+ inertiaZ=0.0351,
+ distanceRocketNozzle=-1.255,
+ distanceRocketPropellant=-0.85704,
+ powerOffDrag=0.6,
+ powerOnDrag=0.6,
+ )
+ test_rocket.setRailButtons([0.2, -0.5])
+
+ NoseCone = test_rocket.addNose(
+ length=0.55829, kind="vonKarman", distanceToCM=0.71971
+ )
+ FinSet = test_rocket.addTrapezoidalFins(
+ 4, span=0.100, rootChord=0.120, tipChord=0.040, distanceToCM=-1.04956
+ )
+ Tail = test_rocket.addTail(
+ topRadius=0.0635, bottomRadius=0.0435, length=0.060, distanceToCM=-1.194656
+ )
+
+ def drogueTrigger(p, y):
+ # p = pressure
+ # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]
+ # activate drogue when vz < 0 m/s.
+ return True if y[5] < 0 else False
+
+ def mainTrigger(p, y):
+ # p = pressure
+ # y = [x, y, z, vx, vy, vz, e0, e1, e2, e3, w1, w2, w3]
+ # activate main when vz < 0 m/s and z < 800 m.
+ return True if y[5] < 0 and y[2] < 800 else False
+
+ Main = test_rocket.addParachute(
+ "Main",
+ CdS=10.0,
+ trigger=mainTrigger,
+ samplingRate=105,
+ lag=1.5,
+ noise=(0, 8.3, 0.5),
+ )
+
+ Drogue = test_rocket.addParachute(
+ "Drogue",
+ CdS=1.0,
+ trigger=drogueTrigger,
+ samplingRate=105,
+ lag=1.5,
+ noise=(0, 8.3, 0.5),
+ )
+
+ test_flight1 = Flight(
+ rocket=test_rocket, environment=test_env, inclination=90, heading=30
+ )
+ test_flight2 = Flight(
+ rocket=test_rocket, environment=test_env, inclination=85, heading=0
+ )
+ test_flight3 = Flight(
+ rocket=test_rocket, environment=test_env, inclination=80, heading=60
+ )
+
+ flight_plotter = flight_plots.flight_plots(
+ [test_flight1, test_flight2, test_flight3]
+ )
+
+ assert flight_plotter.allInfo(mode="basic") == None
+ assert flight_plotter.allInfo(mode="compare") == None