Skip to content

Commit 2c4379f

Browse files
authored
Merge branch 'develop' into copilot/implement-3dof-acceptance-test
2 parents 8756901 + eaa9181 commit 2c4379f

66 files changed

Lines changed: 6824 additions & 169 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ good-names=FlightPhases,
229229
center_of_mass_without_motor_to_CDM,
230230
motor_center_of_dry_mass_to_CDM,
231231
generic_motor_cesaroni_M1520,
232+
Re, # Reynolds number
232233

233234
# Good variable names regexes, separated by a comma. If names match any regex,
234235
# they will always be accepted

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
"outerboundaryis",
242242
"overshootable",
243243
"planform",
244+
"pointmassmotor",
244245
"polystyle",
245246
"powerseries",
246247
"Prandtl",

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,31 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35+
- MNT: net thrust addition to 3 dof in flight class [#907] (https://github.com/RocketPy-Team/RocketPy/pull/907)
36+
- ENH: 3-dof lateral motion improvement [#883](https://github.com/RocketPy-Team/RocketPy/pull/883)
37+
- ENH: Add multi-dimensional drag coefficient support (Cd as function of M, Re, α) [#875](https://github.com/RocketPy-Team/RocketPy/pull/875)
38+
- ENH: Add save functionality to `_MonteCarloPlots.all` method [#848](https://github.com/RocketPy-Team/RocketPy/pull/848)
39+
- ENH: add animations for motor propellant mass and tank fluid volumes [#894](https://github.com/RocketPy-Team/RocketPy/pull/894)
40+
- ENH: Rail button bending moments calculation in Flight class [#893](https://github.com/RocketPy-Team/RocketPy/pull/893)
41+
- ENH: Implement Bootstrapping for Confidence Interval Estimation [#891](https://github.com/RocketPy-Team/RocketPy/pull/897)
42+
- ENH: Built-in flight comparison tool (`FlightComparator`) to validate simulations against external data [#888](https://github.com/RocketPy-Team/RocketPy/pull/888)
43+
- ENH: Add persistent caching for ThrustCurve API [#881](https://github.com/RocketPy-Team/RocketPy/pull/881)
44+
- ENH: Add axial_acceleration attribute to the Flight class [#876](https://github.com/RocketPy-Team/RocketPy/pull/876)
45+
- ENH: custom warning no motor or aerosurface [#871](https://github.com/RocketPy-Team/RocketPy/pull/871)
46+
- ENH: Add thrustcurve api integration to retrieve motor eng data [#870](https://github.com/RocketPy-Team/RocketPy/pull/870)
47+
- ENH: Compatibility with MERRA-2 atmosphere reanalysis files [#825](https://github.com/RocketPy-Team/RocketPy/pull/825)
48+
- ENH: Enable only radial burning [#815](https://github.com/RocketPy-Team/RocketPy/pull/815)
49+
3550
### Changed
3651

52+
-
53+
3754
### Fixed
3855

56+
- BUG: energy_data plot not working for 3 dof sims [[#906](https://github.com/RocketPy-Team/RocketPy/issues/906)]
57+
- BUG: Fix CSV column header spacing in FlightDataExporter [#864](https://github.com/RocketPy-Team/RocketPy/issues/864)
58+
- BUG: Fix parallel Monte Carlo simulation showing incorrect iteration count [#806](https://github.com/RocketPy-Team/RocketPy/pull/806)
59+
3960

4061
## [v1.11.0] - 2025-11-01
4162

@@ -71,6 +92,7 @@ Attention: The newest changes should be on top -->
7192
## [v1.10.0] - 2025-05-16
7293

7394
### Added
95+
7496
- ENH: Support for ND arithmetic in Function class. [#810] (https://github.com/RocketPy-Team/RocketPy/pull/810)
7597
- ENH: allow users to provide custom samplers [#803](https://github.com/RocketPy-Team/RocketPy/pull/803)
7698
- ENH: Implement Multivariate Rejection Sampling (MRS) [#738] (https://github.com/RocketPy-Team/RocketPy/pull/738)

docs/user/environment/1-atm-models/reanalysis.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ ERA5 data can be downloaded from the
4646
processed by RocketPy. It is recommended that you download only the \
4747
necessary data.
4848

49+
MERRA-2
50+
-------
51+
52+
The Modern-Era Retrospective analysis for Research and Applications, Version 2 (MERRA-2) is a NASA atmospheric reanalysis for the satellite era using the Goddard Earth Observing System, Version 5 (GEOS-5) with its Atmospheric Data Assimilation System (ADAS).
53+
54+
You can download these files from the `NASA GES DISC <https://disc.gsfc.nasa.gov/>`_.
55+
56+
To use MERRA-2 data in RocketPy, you generally need the **Assimilated Meteorological Fields** collection (specifically the 3D Pressure Level data, usually named ``inst3_3d_asm_Np``). Note that MERRA-2 files typically use the ``.nc4`` extension (NetCDF-4), which is fully supported by RocketPy.
57+
58+
You can load these files using the ``dictionary="MERRA2"`` argument:
59+
60+
.. code-block:: python
61+
62+
env.set_atmospheric_model(
63+
type="Reanalysis",
64+
file="MERRA2_400.inst3_3d_asm_Np.20230620.nc4",
65+
dictionary="MERRA2"
66+
)
67+
68+
RocketPy automatically handles the unit conversion for MERRA-2's surface geopotential (energy) to geometric height (meters).
69+
4970

5071
Setting the Environment
5172
^^^^^^^^^^^^^^^^^^^^^^^

docs/user/environment/1-atm-models/standard_atmosphere.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ Other profiles can be derived from it, however, winds are automatically set to
2222

2323
env.plots.atmospheric_model()
2424

25-
.. skip one line with |
26-
|
25+
|
2726
2827
The International Standard Atmosphere can also be reset at any time by using the
2928
:meth:`rocketpy.Environment.set_atmospheric_model` method. For example:

docs/user/first_simulation.rst

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ regarding the launch site:
9090

9191
| This roughly corresponds to the location of Spaceport America, New Mexico.
9292
93+
9394
Next, we need to specify the atmospheric model to be used. In this example,
9495
we will get GFS forecasts data from tomorrow.
9596

@@ -110,6 +111,7 @@ of tomorrow:
110111

111112
| Now we set the atmospheric model to be used:
112113
114+
113115
.. jupyter-execute::
114116

115117
env.set_atmospheric_model(type="Forecast", file="GFS")
@@ -189,6 +191,7 @@ to specify several parameters:
189191

190192
| We can see its characteristics by calling the info method:
191193
194+
192195
.. jupyter-execute::
193196

194197
Pro75M1670.info()
@@ -637,8 +640,9 @@ and the rocket Mach number (see :meth:`rocketpy.Flight.mach_number`) to the file
637640
)
638641

639642
| As you can see, the first argument is the file name to be created. The following
640-
arguments are the attributes to be exported. We can check the file by reading it
641-
with :func:`pandas.read_csv`:
643+
arguments are the attributes to be exported. We can check the file by reading it
644+
with :func:`pandas.read_csv`:
645+
642646

643647
.. jupyter-execute::
644648

@@ -647,8 +651,9 @@ with :func:`pandas.read_csv`:
647651
pd.read_csv("calisto_flight_data.csv")
648652

649653
| The file header specifies the meaning of each column. The time samples are
650-
obtained from the simulation solver steps. To export the data at a different
651-
sampling rate, use the ``time_step`` argument:
654+
obtained from the simulation solver steps. To export the data at a different
655+
sampling rate, use the ``time_step`` argument:
656+
652657

653658
.. jupyter-execute::
654659

docs/user/flight.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,39 @@ The Flight object provides access to all forces and accelerations acting on the
266266
M2 = flight.M2 # Pitch moment
267267
M3 = flight.M3 # Yaw moment
268268

269+
Rail Button Forces and Bending Moments
270+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
271+
272+
During the rail launch phase, RocketPy calculates reaction forces and internal bending moments at the rail button attachment points:
273+
274+
**Rail Button Forces (N):**
275+
276+
- ``rail_button1_normal_force`` : Normal reaction force at upper rail button
277+
- ``rail_button1_shear_force`` : Shear (tangential) reaction force at upper rail button
278+
- ``rail_button2_normal_force`` : Normal reaction force at lower rail button
279+
- ``rail_button2_shear_force`` : Shear (tangential) reaction force at lower rail button
280+
281+
**Rail Button Bending Moments (N⋅m):**
282+
283+
- ``rail_button1_bending_moment`` : Time-dependent bending moment at upper rail button attachment
284+
- ``max_rail_button1_bending_moment`` : Maximum absolute bending moment at upper rail button
285+
- ``rail_button2_bending_moment`` : Time-dependent bending moment at lower rail button attachment
286+
- ``max_rail_button2_bending_moment`` : Maximum absolute bending moment at lower rail button
287+
288+
**Calculation Method:**
289+
290+
Bending moments are calculated using beam theory assuming simple supports (rail buttons provide reaction forces but no moment reaction at rail contact). The total moment combines:
291+
292+
1. Shear force × button height (cantilever moment from button standoff)
293+
2. Normal force × distance to center of dry mass (lever arm effect)
294+
295+
Moments are zero after rail departure and represent internal structural loads for airframe and fastener stress analysis. Requires ``button_height`` to be defined when adding rail buttons via ``rocket.set_rail_buttons()``.
296+
297+
.. note::
298+
See Issue #893 for implementation details and validation approach.
299+
300+
301+
269302
Attitude and Orientation
270303
~~~~~~~~~~~~~~~~~~~~~~~~
271304

docs/user/flight_comparator.rst

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
Flight Comparator
2+
=================
3+
4+
This example demonstrates how to use the RocketPy ``FlightComparator`` class to
5+
compare a Flight simulation against external data sources.
6+
7+
Users must explicitly create a `FlightComparator` instance.
8+
9+
10+
This class is designed to compare a RocketPy Flight simulation against external
11+
data sources, such as:
12+
13+
- Real flight data (avionics logs, altimeter CSVs)
14+
- Simulations from other software (OpenRocket, RASAero)
15+
- Theoretical models or manual calculations
16+
17+
Unlike ``CompareFlights`` (which compares multiple RocketPy simulations),
18+
``FlightComparator`` specifically handles the challenge of aligning different
19+
time steps and calculating error metrics (RMSE, MAE, etc.) between a
20+
"Reference" simulation and "External" data.
21+
22+
Importing classes
23+
-----------------
24+
25+
We will start by importing the necessary classes and modules:
26+
27+
.. jupyter-execute::
28+
29+
import numpy as np
30+
31+
from rocketpy import Environment, Rocket, SolidMotor, Flight
32+
from rocketpy.simulation import FlightComparator, FlightDataImporter
33+
34+
Create Simulation (Reference)
35+
-----------------------------
36+
37+
First, let's create the standard RocketPy simulation that will serve as our
38+
"Ground Truth" or reference model. This follows the standard setup.
39+
40+
.. jupyter-execute::
41+
42+
# 1. Setup Environment
43+
env = Environment(
44+
date=(2022, 10, 1, 12),
45+
latitude=32.990254,
46+
longitude=-106.974998,
47+
elevation=1400,
48+
)
49+
env.set_atmospheric_model(type="standard_atmosphere")
50+
51+
# 2. Setup Motor
52+
Pro75M1670 = SolidMotor(
53+
thrust_source="../data/motors/cesaroni/Cesaroni_M1670.eng",
54+
burn_time=3.9,
55+
grain_number=5,
56+
grain_density=1815,
57+
grain_outer_radius=33 / 1000,
58+
grain_initial_inner_radius=15 / 1000,
59+
grain_initial_height=120 / 1000,
60+
grain_separation=5 / 1000,
61+
nozzle_radius=33 / 1000,
62+
throat_radius=11 / 1000,
63+
interpolation_method="linear",
64+
coordinate_system_orientation="nozzle_to_combustion_chamber",
65+
dry_mass=1.815,
66+
dry_inertia=(0.125, 0.125, 0.002),
67+
grains_center_of_mass_position=0.33,
68+
center_of_dry_mass_position=0.317,
69+
nozzle_position=0,
70+
)
71+
72+
# 3. Setup Rocket
73+
calisto = Rocket(
74+
radius=127 / 2000,
75+
mass=19.197 - 2.956,
76+
inertia=(6.321, 6.321, 0.034),
77+
power_off_drag="../data/calisto/powerOffDragCurve.csv",
78+
power_on_drag="../data/calisto/powerOnDragCurve.csv",
79+
center_of_mass_without_motor=0,
80+
coordinate_system_orientation="tail_to_nose",
81+
)
82+
83+
calisto.set_rail_buttons(0.0818, -0.618, 45)
84+
calisto.add_motor(Pro75M1670, position=-1.255)
85+
86+
# Add aerodynamic surfaces
87+
nosecone = calisto.add_nose(length=0.55829, kind="vonKarman", position=0.71971)
88+
fin_set = calisto.add_trapezoidal_fins(
89+
n=4,
90+
root_chord=0.120,
91+
tip_chord=0.040,
92+
span=0.100,
93+
position=-1.04956,
94+
cant_angle=0.5,
95+
airfoil=("../data/calisto/fins/NACA0012-radians.txt", "radians"),
96+
)
97+
tail = calisto.add_tail(
98+
top_radius=0.0635,
99+
bottom_radius=0.0435,
100+
length=0.060,
101+
position=-1.194656,
102+
)
103+
104+
# 4. Simulate
105+
flight = Flight(
106+
rocket=calisto,
107+
environment=env,
108+
rail_length=5.2,
109+
inclination=85,
110+
heading=0,
111+
)
112+
113+
# 5. Create FlightComparator instance
114+
comparator = FlightComparator(flight)
115+
116+
Adding Another Flight Object
117+
----------------------------
118+
119+
You can compare against another RocketPy Flight simulation directly:
120+
121+
.. jupyter-execute::
122+
123+
# Create a second simulation with slightly different parameters
124+
flight2 = Flight(
125+
rocket=calisto,
126+
environment=env,
127+
rail_length=5.0, # Slightly shorter rail
128+
inclination=85,
129+
heading=0,
130+
)
131+
132+
# Add the second flight directly
133+
comparator.add_data("Alternative Sim", flight2)
134+
135+
print(f"Added variables from flight2: {list(comparator.data_sources['Alternative Sim'].keys())}")
136+
137+
Importing External Data (dict)
138+
------------------------------
139+
140+
The primary data format expected by ``FlightComparator.add_data`` is a dictionary
141+
where keys are variable names (e.g. ``"z"``, ``"vz"``, ``"altitude"``) and values
142+
are either:
143+
144+
- A RocketPy ``Function`` object, or
145+
- A tuple of ``(time_array, data_array)``.
146+
147+
Let's create some synthetic external data to compare against our reference
148+
simulation:
149+
150+
.. jupyter-execute::
151+
152+
# Generate synthetic external data with realistic noise
153+
time_external = np.linspace(0, flight.t_final, 80) # Different time steps
154+
external_altitude = flight.z(time_external) + np.random.normal(0, 3, 80) # 3m noise
155+
external_velocity = flight.vz(time_external) + np.random.normal(0, 0.5, 80) # 0.5 m/s noise
156+
157+
# Add the external data to our comparator
158+
comparator.add_data(
159+
"External Simulator",
160+
{
161+
"altitude": (time_external, external_altitude),
162+
"vz": (time_external, external_velocity),
163+
}
164+
)
165+
166+
Running Comparisons
167+
-------------------
168+
169+
Now we can run the various comparison methods:
170+
171+
.. jupyter-execute::
172+
173+
# Generate summary with key events
174+
comparator.summary()
175+
176+
# Compare specific variable
177+
comparator.compare("altitude")
178+
179+
# Compare all available variables
180+
comparator.all()
181+
182+
# Plot 2D trajectory
183+
comparator.trajectories_2d(plane="xz")

docs/user/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ RocketPy's User Guide
77

88
Installation and Requirements <installation.rst>
99
First Simulation <first_simulation.rst>
10+
3 DOF Simulations and Comparison <three_dof_simulation.rst>
1011

1112
.. toctree::
1213
:maxdepth: 1
13-
:caption: Main Classes Usage
14+
:caption: Main Classes Usage
1415

1516
Positions and Coordinate Systems <positions.rst>
1617
Motors <motors/motors.rst>

0 commit comments

Comments
 (0)