Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Attention: The newest changes should be on top -->

### Added

- ENH: Add save functionality to `_MonteCarloPlots.all` method [#848](https://github.com/RocketPy-Team/RocketPy/pull/848)
- ENH: Built-in flight comparison tool (`FlightComparator`) to validate simulations against external data [#888](https://github.com/RocketPy-Team/RocketPy/pull/888)
- ENH: Add persistent caching for ThrustCurve API [#881](https://github.com/RocketPy-Team/RocketPy/pull/881)
- ENH: Compatibility with MERRA-2 atmosphere reanalysis files [#825](https://github.com/RocketPy-Team/RocketPy/pull/825)
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
Expand Down
344 changes: 149 additions & 195 deletions docs/user/compare_flights.rst
Original file line number Diff line number Diff line change
@@ -1,229 +1,183 @@
Compare Flights
===============
Flight Comparator
=================

This example demonstrates how to use the rocketpy ``CompareFlights`` class.
This class has many applications, including the comparison of different flight
setups for a single rocket, the simulation of deployable systems, and the
multi-stage rocket analysis.
This example demonstrates how to use the RocketPy ``FlightComparator`` class to
compare a Flight simulation against external data sources.

Importing classes
-----------------

We will start by importing the necessary classes and modules:

.. jupyter-execute::

from rocketpy.plots.compare import CompareFlights
from rocketpy import Environment, Flight, Rocket, SolidMotor
from datetime import datetime, timedelta


Create Environment, Motor and Rocket
------------------------------------

First, let's create the environment, motor and rocket objects.
This is done following the same steps as in the :ref:`firstsimulation` example.

.. jupyter-execute::

after_tomorrow = datetime.now() + timedelta(days=2)
env = Environment(latitude=-23, longitude=-49, date=after_tomorrow)
env.set_atmospheric_model(type="Forecast", file="GFS")

cesaroni_motor = SolidMotor(
thrust_source="../data/motors/cesaroni/Cesaroni_M1670.eng",
dry_mass=1.815,
dry_inertia=(0.125, 0.125, 0.002),
nozzle_radius=33 / 1000,
grain_number=5,
grain_density=1815,
grain_outer_radius=33 / 1000,
grain_initial_inner_radius=15 / 1000,
grain_initial_height=120 / 1000,
grain_separation=5 / 1000,
grains_center_of_mass_position=0.397,
center_of_dry_mass_position=0.317,
nozzle_position=0,
burn_time=3.9,
throat_radius=11 / 1000,
coordinate_system_orientation="nozzle_to_combustion_chamber",
)

calisto = Rocket(
radius=127 / 2000,
mass=14.426,
inertia=(6.321, 6.321, 0.034),
power_off_drag="../data/rockets/calisto/powerOffDragCurve.csv",
power_on_drag="../data/rockets/calisto/powerOnDragCurve.csv",
center_of_mass_without_motor=0,
coordinate_system_orientation="tail_to_nose",
)

calisto.set_rail_buttons(
upper_button_position=0.0818,
lower_button_position=-0.618,
angular_position=45,
)

calisto.add_motor(cesaroni_motor, position=-1.255)

nosecone = calisto.add_nose(length=0.55829, kind="vonKarman", position=1.278)

fin_set = calisto.add_trapezoidal_fins(
n=4,
root_chord=0.120,
tip_chord=0.060,
span=0.110,
position=-1.04956,
cant_angle=0.5,
airfoil=("../data/airfoils/NACA0012-radians.txt", "radians"),
)

tail = calisto.add_tail(
top_radius=0.0635, bottom_radius=0.0435, length=0.060, position=-1.194656
)

main_chute = calisto.add_parachute(
"Main",
cd_s=10.0,
trigger=800,
sampling_rate=105,
lag=1.5,
noise=(0, 8.3, 0.5),
)

drogue_chute = calisto.add_parachute(
"Drogue",
cd_s=1.0,
trigger="apogee",
sampling_rate=105,
lag=1.5,
noise=(0, 8.3, 0.5),
)

Creating the Flight objects
---------------------------

Now we can create different flights varying the launch angle and the rail inclination:

.. jupyter-execute::
Users must explicitly create a `FlightComparator` instance.

inclinations = [85, 75]
headings = [90, 135]
flights = []

for heading in headings:
for inclination in inclinations:
flight = Flight(
environment=env,
rocket=calisto,
rail_length=5.2,
inclination=inclination,
heading=heading,
name=f"Incl {inclination} Head {heading}",
)
flights.append(flight)
This class is designed to compare a RocketPy Flight simulation against external
data sources, such as:

- Real flight data (avionics logs, altimeter CSVs)
- Simulations from other software (OpenRocket, RASAero)
- Theoretical models or manual calculations

We can easily visualize the number of flights created:
Unlike ``CompareFlights`` (which compares multiple RocketPy simulations),
``FlightComparator`` specifically handles the challenge of aligning different
time steps and calculating error metrics (RMSE, MAE, etc.) between a
"Reference" simulation and "External" data.

.. jupyter-execute::

print("Number of flights: ", len(flights))

Start the comparison
--------------------
Importing classes
-----------------

It is easy to initialize the ``CompareFlights`` object:
We will start by importing the necessary classes and modules:

.. jupyter-execute::

comparison = CompareFlights(flights)


After the initialization, we can use different methods to plot the results in a comparative way.
To see a full description of the available methods, you can check the :ref:`compareflights` documentation.

Plotting results one by one
import numpy as np

from rocketpy import Environment, Rocket, SolidMotor, Flight
from rocketpy.simulation import FlightComparator, FlightDataImporter

Create Simulation (Reference)
-----------------------------

First, let's create the standard RocketPy simulation that will serve as our
"Ground Truth" or reference model. This follows the standard setup.

.. jupyter-execute::

# 1. Setup Environment
env = Environment(
date=(2022, 10, 1, 12),
latitude=32.990254,
longitude=-106.974998,
elevation=1400,
)
env.set_atmospheric_model(type="standard_atmosphere")

# 2. Setup Motor
Pro75M1670 = SolidMotor(
thrust_source="../data/motors/cesaroni/Cesaroni_M1670.eng",
burn_time=3.9,
grain_number=5,
grain_density=1815,
grain_outer_radius=33 / 1000,
grain_initial_inner_radius=15 / 1000,
grain_initial_height=120 / 1000,
grain_separation=5 / 1000,
nozzle_radius=33 / 1000,
throat_radius=11 / 1000,
interpolation_method="linear",
coordinate_system_orientation="nozzle_to_combustion_chamber",
dry_mass=1.815,
dry_inertia=(0.125, 0.125, 0.002),
grains_center_of_mass_position=0.33,
center_of_dry_mass_position=0.317,
nozzle_position=0,
)

# 3. Setup Rocket
calisto = Rocket(
radius=127 / 2000,
mass=19.197 - 2.956,
inertia=(6.321, 6.321, 0.034),
power_off_drag="../data/calisto/powerOffDragCurve.csv",
power_on_drag="../data/calisto/powerOnDragCurve.csv",
center_of_mass_without_motor=0,
coordinate_system_orientation="tail_to_nose",
)

calisto.set_rail_buttons(0.0818, -0.618, 45)
calisto.add_motor(Pro75M1670, position=-1.255)

# Add aerodynamic surfaces
nosecone = calisto.add_nose(length=0.55829, kind="vonKarman", position=0.71971)
fin_set = calisto.add_trapezoidal_fins(
n=4,
root_chord=0.120,
tip_chord=0.040,
span=0.100,
position=-1.04956,
cant_angle=0.5,
airfoil=("../data/calisto/fins/NACA0012-radians.txt", "radians"),
)
tail = calisto.add_tail(
top_radius=0.0635,
bottom_radius=0.0435,
length=0.060,
position=-1.194656,
)

# 4. Simulate
flight = Flight(
rocket=calisto,
environment=env,
rail_length=5.2,
inclination=85,
heading=0,
)

# 5. Create FlightComparator instance
comparator = FlightComparator(flight)

Adding Another Flight Object
----------------------------

The flights results are divided into different methods, so we can plot them one by one.
This is practical when we want to focus on a specific aspect of the flights.

.. jupyter-execute::

comparison.trajectories_3d(legend=True)

.. jupyter-execute::

comparison.positions()
You can compare against another RocketPy Flight simulation directly:

.. jupyter-execute::

comparison.trajectories_2d(plane="xy", legend=True)
# Create a second simulation with slightly different parameters
flight2 = Flight(
rocket=calisto,
environment=env,
rail_length=5.0, # Slightly shorter rail
inclination=85,
heading=0,
)

.. jupyter-execute::

comparison.velocities()

.. jupyter-execute::

comparison.stream_velocities()

.. jupyter-execute::

comparison.accelerations()

.. jupyter-execute::

comparison.angular_velocities()

.. jupyter-execute::

comparison.angular_accelerations()

.. jupyter-execute::
# Add the second flight directly
comparator.add_data("Alternative Sim", flight2)

comparison.attitude_angles()
print(f"Added variables from flight2: {list(comparator.data_sources['Alternative Sim'].keys())}")

.. jupyter-execute::
Importing External Data (dict)
------------------------------

comparison.euler_angles()
The primary data format expected by ``FlightComparator.add_data`` is a dictionary
where keys are variable names (e.g. ``"z"``, ``"vz"``, ``"altitude"``) and values
are either:

.. jupyter-execute::
- A RocketPy ``Function`` object, or
- A tuple of ``(time_array, data_array)``.

comparison.quaternions()
Let's create some synthetic external data to compare against our reference
simulation:

.. jupyter-execute::

comparison.angles_of_attack()

.. jupyter-execute::
# Generate synthetic external data with realistic noise
time_external = np.linspace(0, flight.t_final, 80) # Different time steps
external_altitude = flight.z(time_external) + np.random.normal(0, 3, 80) # 3m noise
external_velocity = flight.vz(time_external) + np.random.normal(0, 0.5, 80) # 0.5 m/s noise

comparison.aerodynamic_forces()
# Add the external data to our comparator
comparator.add_data(
"External Simulator",
{
"altitude": (time_external, external_altitude),
"vz": (time_external, external_velocity),
}
)

.. jupyter-execute::
Running Comparisons
-------------------

comparison.aerodynamic_moments()
Now we can run the various comparison methods:

.. jupyter-execute::

comparison.fluid_mechanics()

.. jupyter-execute::
# Generate summary with key events
comparator.summary()

comparison.energies()
# Compare specific variable
comparator.compare("altitude")

.. jupyter-execute::

comparison.powers()


Plotting using the ``all`` method
---------------------------------

Alternatively, we can plot the results altogether by calling one simple method:

.. jupyter-execute::
# Compare all available variables
comparator.all()

comparison.all()
# Plot 2D trajectory
comparator.trajectories_2d(plane="xz")
Comment thread
Monta120 marked this conversation as resolved.
Outdated
Loading