44import numpy as np
55
66from rocketpy .mathutils import Function
7+ from rocketpy .simulation .flight import Flight
78from rocketpy .simulation .flight_data_importer import FlightDataImporter
89
910from ..plots .plot_helpers import show_or_save_fig
@@ -15,7 +16,9 @@ class FlightComparator:
1516 (such as flight logs, OpenRocket simulations, RASAero).
1617
1718 This class handles the time-interpolation required to compare datasets
18- recorded at different frequencies.
19+ recorded at different frequencies, and computes error metrics (RMSE, MAE, etc.)
20+ between your RocketPy simulation and external or reference data.
21+
1922
2023 Parameters
2124 ----------
@@ -35,15 +38,20 @@ class FlightComparator:
3538
3639 .. code-block:: python
3740
38- # Assuming you have a Flight object named 'my_flight'
41+ from rocketpy.simulation import FlightComparator
42+
43+ # Suppose you have a Flight object named 'my_flight'
3944 comparator = FlightComparator(my_flight)
4045
41- # Add external data
46+ # Add external data (e.g., from OpenRocket or logs)
4247 comparator.add_data('OpenRocket', {
4348 'altitude': (time_array, altitude_array),
4449 'vz': (time_array, velocity_array)
4550 })
4651
52+ # You can also add another RocketPy Flight directly:
53+ comparator.add_data('OtherSimulation', other_flight)
54+
4755 # Run comparisons
4856 comparator.compare('altitude')
4957 comparator.summary()
@@ -52,7 +60,7 @@ class FlightComparator:
5260
5361 DEFAULT_GRID_POINTS = 1000 # number of points for interpolation grids
5462
55- def __init__ (self , flight ):
63+ def __init__ (self , flight : Flight ):
5664 """
5765 Initialize the comparator with a reference RocketPy Flight.
5866
@@ -65,19 +73,19 @@ def __init__(self, flight):
6573 -------
6674 None
6775 """
68- # Minimal duck -typed validation to give clear errors early
76+ # Duck -typed validation gives clear errors for Flight-like objects, more flexible than an isinstance check
6977 required_attrs = ("t_final" , "apogee" , "apogee_time" , "impact_velocity" )
7078 missing = [attr for attr in required_attrs if not hasattr (flight , attr )]
7179 if missing :
7280 raise TypeError (
73- "flight must be a rocketpy.Flight-like object with attributes "
81+ "flight must be a rocketpy.Flight or Flight -like object with attributes "
7482 f"{ required_attrs } . Missing: { ', ' .join (missing )} "
7583 )
7684
7785 self .flight = flight
7886 self .data_sources = {} # The format is {'Source Name': {'variable': Function}}
7987
80- def add_data (self , label , data_dict ): # pylint: disable=too-many-statements,import-outside-toplevel,cyclic-import
88+ def add_data (self , label , data_dict ): # pylint: disable=too-many-statements
8189 """
8290 Add an external dataset to the comparator.
8391
@@ -102,9 +110,8 @@ def add_data(self, label, data_dict): # pylint: disable=too-many-statements,imp
102110 if present.
103111 """
104112
105- from rocketpy .simulation .flight import (
106- Flight ,
107- )
113+ if isinstance (data_dict , dict ) and not data_dict :
114+ raise ValueError ("data_dict cannot be empty" )
108115
109116 processed_data = {}
110117
@@ -197,45 +204,9 @@ def add_data(self, label, data_dict): # pylint: disable=too-many-statements,imp
197204 "or FlightDataImporter object."
198205 )
199206
200- self .data_sources [label ] = processed_data
201- print (
202- f"Added data source '{ label } ' with variables: { list (processed_data .keys ())} "
203- )
204- # If this is not a dict (e.g., a Flight or FlightDataImporter), we're done.
205- if not isinstance (data_dict , dict ):
206- return
207-
208- # Check if label already exists
209207 if label in self .data_sources :
210208 warnings .warn (f"Data source '{ label } ' already exists. Overwriting." )
211209
212- # Making sure that data_dict is not empty
213- if not data_dict :
214- raise ValueError ("data_dict cannot be empty" )
215-
216- processed_data = {}
217-
218- for key , value in data_dict .items ():
219- # If already a Function, store it
220- if isinstance (value , Function ):
221- processed_data [key ] = value
222-
223- # If raw data, convert to a function
224- elif isinstance (value , (tuple , list )) and len (value ) == 2 :
225- time_arr , data_arr = value
226- # Creating a Function for automatic interpolation
227- processed_data [key ] = Function (
228- np .column_stack ((time_arr , data_arr )),
229- inputs = "Time (s)" ,
230- outputs = key ,
231- interpolation = "linear" ,
232- )
233- else :
234- warnings .warn (
235- f"Skipping '{ key } ' in '{ label } '. Format not recognized. "
236- "Expected RocketPy Function or (time, data) tuple."
237- )
238-
239210 self .data_sources [label ] = processed_data
240211 print (
241212 f"Added data source '{ label } ' with variables: { list (processed_data .keys ())} "
0 commit comments