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 \n \n \n \n 2022-10-05T13:00:32.147998\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T13:00:32.756529\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T13:01:32.265013\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T13:01:34.682136\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T13:01:44.814876\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T18:56:18.405065\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T18:56:19.481274\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T18:56:20.705799\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T18:56:22.555743\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T21:53:20.044937\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:20.380565\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:20.876126\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:21.421663\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:22.227653\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:22.657046\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:23.180644\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:23.749305\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:24.698796\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:25.314778\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:25.590042\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:25.815438\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:26.053799\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:26.397878\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:26.849669\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:27.561764\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:28.300787\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:29.002908\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-05T21:53:29.683087\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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 \n \n \n \n 2022-10-05T21:52:43.226437\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \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