From 9fe3a37c93069156ac45a6f241f5935832ddb21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Fri, 14 Mar 2025 18:46:09 +0100 Subject: [PATCH 01/19] wind factor bug corrected the wind factor wasn't applied to the env.wind_velocity properties --- rocketpy/stochastic/stochastic_environment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rocketpy/stochastic/stochastic_environment.py b/rocketpy/stochastic/stochastic_environment.py index 58afe0fed..e0fc33eec 100644 --- a/rocketpy/stochastic/stochastic_environment.py +++ b/rocketpy/stochastic/stochastic_environment.py @@ -184,5 +184,6 @@ def create_object(self): # get original attribute value and multiply by factor attribute_name = f"_{key.replace('_factor', '')}" value = getattr(self, attribute_name) * value + key = f"{key.replace('_factor', '')}" setattr(self.obj, key, value) return self.obj From f6efa816442a740279ebaf880458049d28799b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 11:46:06 +0100 Subject: [PATCH 02/19] BUG: StochasticModel visualize attributes of a uniform distribution It showed the nominal and the standard deviation values and it doesn't make sense in a uniform distribution. In a np.random.uniform the 'nominal value' is the lower bound of the distribution, and the 'standard deviation' value is the upper bound. Now, a new condition has been added for the uniform distributions where the mean and semi range are calculated and showed. This way the visualize_attribute function will show the whole range where the random values are uniformly taken in --- rocketpy/stochastic/stochastic_model.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index c82232fcb..e44e2613d 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -486,11 +486,20 @@ def format_attribute(attr, value): ) elif isinstance(value, tuple): nominal_value, std_dev, dist_func = value - return ( - f"\t{attr.ljust(max_str_length)} " - f"{nominal_value:.5f} ± " - f"{std_dev:.5f} ({dist_func.__name__})" - ) + if dist_func == np.random.uniform: + mean = (std_dev + nominal_value) / 2 + semi_range = (std_dev - nominal_value) / 2 + return ( + f"\t{attr.ljust(max_str_length)} " + f"{mean:.5f} ± " + f"{semi_range:.5f} ({dist_func.__name__})" + ) + else: + return ( + f"\t{attr.ljust(max_str_length)} " + f"{nominal_value:.5f} ± " + f"{std_dev:.5f} ({dist_func.__name__})" + ) return None attributes = {k: v for k, v in self.__dict__.items() if not k.startswith("_")} From 04337ab79982f28196d4b3950d36407cac6752b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 16:11:27 +0100 Subject: [PATCH 03/19] variable names corrections --- rocketpy/stochastic/stochastic_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index e44e2613d..e833e01bf 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -488,11 +488,11 @@ def format_attribute(attr, value): nominal_value, std_dev, dist_func = value if dist_func == np.random.uniform: mean = (std_dev + nominal_value) / 2 - semi_range = (std_dev - nominal_value) / 2 + half_range = (std_dev - nominal_value) / 2 return ( f"\t{attr.ljust(max_str_length)} " f"{mean:.5f} ± " - f"{semi_range:.5f} ({dist_func.__name__})" + f"{half_range:.5f} ({dist_func.__name__})" ) else: return ( From 18f8323f39798ae90caada573660dcf63b113769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sat, 15 Mar 2025 17:46:04 +0100 Subject: [PATCH 04/19] Corrections requested by the pylint test --- rocketpy/stochastic/stochastic_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/stochastic/stochastic_model.py b/rocketpy/stochastic/stochastic_model.py index e833e01bf..39fc478b7 100644 --- a/rocketpy/stochastic/stochastic_model.py +++ b/rocketpy/stochastic/stochastic_model.py @@ -486,7 +486,7 @@ def format_attribute(attr, value): ) elif isinstance(value, tuple): nominal_value, std_dev, dist_func = value - if dist_func == np.random.uniform: + if callable(dist_func) and dist_func.__name__ == "uniform": mean = (std_dev + nominal_value) / 2 half_range = (std_dev - nominal_value) / 2 return ( From a46de46eaa87bd8de797e030738facdbe4492587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 16 Mar 2025 13:21:19 +0100 Subject: [PATCH 05/19] ENH: add multiplication for 2D functions in rocketpy.function Added the ability to multiply functions with 2D domains in the __mul__ function --- rocketpy/mathutils/function.py | 89 ++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 143912357..869f5a7b9 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -2222,7 +2222,7 @@ def __rsub__(self, other): def __mul__(self, other): """Multiplies a Function object and returns a new Function object which gives the result of the multiplication. Only implemented for 1D - domains. + and 2D domains. Parameters ---------- @@ -2238,7 +2238,7 @@ def __mul__(self, other): Returns ------- result : Function - A Function object which gives the result of self(x)*other(x). + A Function object which gives the result of self(x,y)*other(x,y). """ self_source_is_array = isinstance(self.source, np.ndarray) other_source_is_array = ( @@ -2250,37 +2250,66 @@ def __mul__(self, other): interp = self.__interpolation__ extrap = self.__extrapolation__ - if ( - self_source_is_array - and other_source_is_array - and np.array_equal(self.x_array, other.x_array) - ): - source = np.column_stack((self.x_array, self.y_array * other.y_array)) - outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})" - return Function(source, inputs, outputs, interp, extrap) - elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array( - other - ): - if not self_source_is_array: - return Function(lambda x: (self.get_value_opt(x) * other), inputs) - source = np.column_stack((self.x_array, np.multiply(self.y_array, other))) - outputs = f"({self.__outputs__[0]}*{other})" - return Function( - source, - inputs, - outputs, - interp, - extrap, - ) - elif callable(other): - return Function(lambda x: (self.get_value_opt(x) * other(x)), inputs) - else: - raise TypeError("Unsupported type for multiplication") + if self.__dom_dim__ == 1: + if ( + self_source_is_array + and other_source_is_array + and np.array_equal(self.x_array, other.x_array) + ): + source = np.column_stack((self.x_array, self.y_array * other.y_array)) + outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})" + return Function(source, inputs, outputs, interp, extrap) + elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array( + other + ): + if not self_source_is_array: + return Function(lambda x: (self.get_value_opt(x) * other), inputs) + source = np.column_stack((self.x_array, np.multiply(self.y_array, other))) + outputs = f"({self.__outputs__[0]}*{other})" + return Function( + source, + inputs, + outputs, + interp, + extrap, + ) + elif callable(other): + return Function(lambda x: (self.get_value_opt(x) * other(x)), inputs) + else: + raise TypeError("Unsupported type for multiplication") + elif self.__dom_dim__ == 2: + if ( + self_source_is_array + and other_source_is_array + and np.array_equal(self.x_array, other.x_array) + and np.array_equal(self.y_array, other.y_array) + ): + source = np.column_stack((self.x_array, self.y_array, self.z_array * other.z_array)) + outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})" + return Function(source, inputs, outputs, interp, extrap) + elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array( + other + ): + if not self_source_is_array: + return Function(lambda x,y: (self.get_value_opt(x,y) * other), inputs) + source = np.column_stack((self.x_array, self.y_array, np.multiply(self.z_array, other))) + outputs = f"({self.__outputs__[0]}*{other})" + return Function( + source, + inputs, + outputs, + interp, + extrap, + ) + elif callable(other): + return Function(lambda x,y: (self.get_value_opt(x,y) * other(x)), inputs) + else: + raise TypeError("Unsupported type for multiplication") def __rmul__(self, other): """Multiplies 'other' by a Function object and returns a new Function object which gives the result of the multiplication. Only implemented for - 1D domains. + 1D and 2D domains. Parameters ---------- @@ -2290,7 +2319,7 @@ def __rmul__(self, other): Returns ------- result : Function - A Function object which gives the result of other(x)*self(x). + A Function object which gives the result of other(x,y)*self(x,y). """ return self * other From 718136045b7678a2c3175aee7a1821e8c5387f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 16 Mar 2025 13:30:46 +0100 Subject: [PATCH 06/19] ENH: StochasticAirBrakes class created The StochasticAirBrakes class has been created. The __init__.py files in the stochastic and rocketpy folders have also been modified accordingly to incorporate this new class --- rocketpy/__init__.py | 1 + rocketpy/stochastic/__init__.py | 1 + .../stochastic/stochastic_aero_surfaces.py | 90 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/rocketpy/__init__.py b/rocketpy/__init__.py index 539b8b2cb..e0436bf02 100644 --- a/rocketpy/__init__.py +++ b/rocketpy/__init__.py @@ -44,6 +44,7 @@ from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope from .simulation import Flight, MonteCarlo from .stochastic import ( + StochasticAirBrakes, StochasticEllipticalFins, StochasticEnvironment, StochasticFlight, diff --git a/rocketpy/stochastic/__init__.py b/rocketpy/stochastic/__init__.py index 692eca85f..b1e146246 100644 --- a/rocketpy/stochastic/__init__.py +++ b/rocketpy/stochastic/__init__.py @@ -6,6 +6,7 @@ """ from .stochastic_aero_surfaces import ( + StochasticAirBrakes, StochasticEllipticalFins, StochasticNoseCone, StochasticRailButtons, diff --git a/rocketpy/stochastic/stochastic_aero_surfaces.py b/rocketpy/stochastic/stochastic_aero_surfaces.py index 31c1e9efc..17f22337b 100644 --- a/rocketpy/stochastic/stochastic_aero_surfaces.py +++ b/rocketpy/stochastic/stochastic_aero_surfaces.py @@ -9,6 +9,7 @@ RailButtons, Tail, TrapezoidalFins, + AirBrakes, ) from .stochastic_model import StochasticModel @@ -432,3 +433,92 @@ def create_object(self): """ generated_dict = next(self.dict_generator()) return RailButtons(**generated_dict) + + +class StochasticAirBrakes(StochasticModel): + """A Stochastic Air Brakes class that inherits from StochasticModel. + + See Also + -------- + :ref:`stochastic_model` and + :class:`AirBrakes ` + + Attributes + ---------- + object : AirBrakes + AirBrakes object to be used for validation. + drag_coefficient_curve : list + The drag coefficient curve of the air brakes can account for + either the air brakes' drag alone or the combined drag of both + the rocket and the air brakes. + drag_coefficient_curve_factor : tuple, list, int, float + The drag curve factor of the air brakes. + reference_area : tuple, list, int, float + Reference area used to non-dimensionalize the drag coefficients. + deployment_level : tuple, list, int, float + Initial deployment level, ranging from 0 to 1. + name : list[str] + List with the air brakes object name. This attribute can't be randomized. + """ + + def __init__( + self, + air_brakes, + drag_coefficient_curve=None, + drag_coefficient_curve_factor=(1, 0), + reference_area=None, + clamp=None, + override_rocket_drag=None, + deployment_level=(0,0), + ): + """Initializes the Stochastic AirBrakes class. + + See Also + -------- + :ref:`stochastic_model` + + Parameters + ---------- + air_brakes : AirBrakes + AirBrakes object to be used for validation. + drag_coefficient_curve : list, optional + The drag coefficient curve of the air brakes can account for + either the air brakes' drag alone or the combined drag of both + the rocket and the air brakes. + drag_coefficient_curve_factor : tuple, list, int, float, optional + The drag curve factor of the air brakes. + reference_area : tuple, list, int, float, optional + Reference area used to non-dimensionalize the drag coefficients. + deployment_level : tuple, list, int, float, optional + Initial deployment level, ranging from 0 to 1. + """ + super().__init__( + air_brakes, + drag_coefficient_curve=drag_coefficient_curve, + drag_coefficient_curve_factor=drag_coefficient_curve_factor, + reference_area=reference_area, + clamp=clamp, + override_rocket_drag=override_rocket_drag, + deployment_level=deployment_level, + name=None, + ) + + def create_object(self): + """Creates and returns an AirBrakes object from the randomly generated + input arguments. + + Returns + ------- + air_brake : AirBrakes + AirBrakes object with the randomly generated input arguments. + """ + generated_dict = next(self.dict_generator()) + air_brakes = AirBrakes( + drag_coefficient_curve=generated_dict['drag_coefficient_curve'], + reference_area=generated_dict['reference_area'], + clamp=generated_dict['clamp'], + override_rocket_drag=generated_dict['override_rocket_drag'], + deployment_level=generated_dict['deployment_level'], + ) + air_brakes.drag_coefficient *= generated_dict['drag_coefficient_curve_factor'] + return air_brakes From e1549e0a2ba12302b617939544ea48f8c895e219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 10:33:59 +0100 Subject: [PATCH 07/19] ENH: set_air_brakes function created This functions appends an airbrake and controller objects previuosly created to the rocket --- rocketpy/rocket/rocket.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 609e1b76c..e40b71a9c 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1534,6 +1534,15 @@ def add_sensor(self, sensor, position): except KeyError: sensor._attached_rockets[self] = 1 + def set_air_brakes( + self, + air_brakes, + controller, + ): + + self.air_brakes.append(air_brakes) + self._add_controllers(controller) + def add_air_brakes( self, drag_coefficient_curve, From 2d2a0e870d5e1741460de132ed010e621164a90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 11:08:00 +0100 Subject: [PATCH 08/19] ENH: add StochasticAirBrake to rocketpy.stochastic_rocket Some functions has been modified and other has been created in order to include the new StochasticAirBrakes feature into the StochasticRocket class. A new function named 'add_air_brakes' has been created to append a StochasticAirBrakes and Controller objects to the StochasticRocket object. A new function '_create_air_brake' has been introduced to create a sample of an AirBrake object through a StochasticAirBrake object. Enventually, the 'create_object' function has been modified to add the sampled AirBrakes to the sampled Rocket --- rocketpy/stochastic/stochastic_rocket.py | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 224262ff1..c8f91b342 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -3,11 +3,13 @@ import warnings from random import choice +from rocketpy.control import _Controller from rocketpy.mathutils.vector_matrix import Vector from rocketpy.motors.empty_motor import EmptyMotor from rocketpy.motors.motor import GenericMotor, Motor from rocketpy.motors.solid_motor import SolidMotor from rocketpy.rocket.aero_surface import ( + AirBrakes, EllipticalFins, NoseCone, RailButtons, @@ -21,6 +23,7 @@ from rocketpy.stochastic.stochastic_motor_model import StochasticMotorModel from .stochastic_aero_surfaces import ( + StochasticAirBrakes, StochasticEllipticalFins, StochasticNoseCone, StochasticRailButtons, @@ -148,6 +151,7 @@ def __init__( self.motors = Components() self.aerodynamic_surfaces = Components() self.rail_buttons = Components() + self.air_brakes = [] self.parachutes = [] self.__components_map = {} super().__init__( @@ -389,6 +393,36 @@ def set_rail_buttons( rail_buttons, self._validate_position(rail_buttons, lower_button_position) ) + def add_air_brakes( + self, + air_brakes, + controller_function, + sampling_rate, + initial_observed_variables=None, + return_controller=False, + controller_name="AirBrakes Controller", + ): + + # checks if input is a StochasticAirbrakes type + if not isinstance(air_brakes, (AirBrakes, StochasticAirBrakes)): + raise TypeError( + "`air_brake` must be of AirBrakes or StochasticAirBrakes type" + ) + if isinstance(air_brakes, AirBrakes): + air_brakes = StochasticAirBrakes(air_brakes=air_brakes) + + self.air_brakes.append(air_brakes) + _controller = _Controller( + interactive_objects=air_brakes, + controller_function=controller_function, + sampling_rate=sampling_rate, + initial_observed_variables=initial_observed_variables, + name=controller_name, + ) + self.air_brake_controller = _controller + if return_controller: + return _controller + def _validate_position(self, validated_object, position): """Validate the position argument. @@ -531,6 +565,7 @@ def dict_generator(self): generated_dict["motors"] = [] generated_dict["aerodynamic_surfaces"] = [] generated_dict["rail_buttons"] = [] + generated_dict["air_brakes"] = [] generated_dict["parachutes"] = [] self.last_rnd_dict = generated_dict yield generated_dict @@ -572,6 +607,11 @@ def _create_rail_buttons(self, component_stochastic_rail_buttons): upper_button_position_rnd ) return rail_buttons, lower_button_position_rnd, upper_button_position_rnd + + def _create_air_brake(self, stochastic_air_brake): + air_brake = stochastic_air_brake.create_object() + self.last_rnd_dict["air_brakes"].append(stochastic_air_brake.last_rnd_dict) + return air_brake def _create_parachute(self, stochastic_parachute): parachute = stochastic_parachute.create_object() @@ -617,6 +657,13 @@ def create_object(self): surface, position_rnd = self._create_surface(component_surface) rocket.add_surfaces(surface, position_rnd) + for air_brake in self.air_brakes: + air_brake = self._create_air_brake(air_brake) + rocket.set_air_brakes( + air_brakes=air_brake, + controller=self.air_brake_controller, + ) + for component_rail_buttons in self.rail_buttons: ( rail_buttons, From b2066478521064c0d3ffd83552b4610c596e1488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 12:36:33 +0100 Subject: [PATCH 09/19] BUG: StochasticAirBrake object input in _Controller When defining the _Controller object a StochasticAirBrake was input. This is already corrected and a AirBrake object is now introduced --- rocketpy/stochastic/stochastic_rocket.py | 25 +++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index c8f91b342..399ea568d 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -396,11 +396,7 @@ def set_rail_buttons( def add_air_brakes( self, air_brakes, - controller_function, - sampling_rate, - initial_observed_variables=None, - return_controller=False, - controller_name="AirBrakes Controller", + controller ): # checks if input is a StochasticAirbrakes type @@ -412,16 +408,7 @@ def add_air_brakes( air_brakes = StochasticAirBrakes(air_brakes=air_brakes) self.air_brakes.append(air_brakes) - _controller = _Controller( - interactive_objects=air_brakes, - controller_function=controller_function, - sampling_rate=sampling_rate, - initial_observed_variables=initial_observed_variables, - name=controller_name, - ) - self.air_brake_controller = _controller - if return_controller: - return _controller + self.air_brake_controller = controller def _validate_position(self, validated_object, position): """Validate the position argument. @@ -659,9 +646,15 @@ def create_object(self): for air_brake in self.air_brakes: air_brake = self._create_air_brake(air_brake) + _controller = _Controller( + interactive_objects=air_brake, + controller_function=self.air_brake_controller.base_controller_function, + sampling_rate=self.air_brake_controller.sampling_rate, + initial_observed_variables=self.air_brake_controller.initial_observed_variables, + ) rocket.set_air_brakes( air_brakes=air_brake, - controller=self.air_brake_controller, + controller=_controller, ) for component_rail_buttons in self.rail_buttons: From e45ae76bf1b8fa4db78cd2c8c818ee1f4fd2d9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 13:47:43 +0100 Subject: [PATCH 10/19] ENH: add time_overshoot option to rocketpy.stochastic_flight Since the new StochasticAirBrake class is defined, we need the 'time_overshoot' option in the Flight class to ensure that the time step defined in the simulation is the controller sampling rate. The MonteCarlo class has had to be modified as well to include this option. --- rocketpy/simulation/monte_carlo.py | 1 + rocketpy/stochastic/stochastic_flight.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/rocketpy/simulation/monte_carlo.py b/rocketpy/simulation/monte_carlo.py index 3914f1e54..ff86db7b3 100644 --- a/rocketpy/simulation/monte_carlo.py +++ b/rocketpy/simulation/monte_carlo.py @@ -458,6 +458,7 @@ def __run_single_simulation(self): heading=self.flight._randomize_heading(), initial_solution=self.flight.initial_solution, terminate_on_apogee=self.flight.terminate_on_apogee, + time_overshoot=self.flight.time_overshoot, ) def __evaluate_flight_inputs(self, sim_idx): diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index 4ed778eb5..ed9037088 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -29,6 +29,9 @@ class StochasticFlight(StochasticModel): terminate_on_apogee : bool Whether or not the flight should terminate on apogee. This attribute can not be randomized. + time_overshoot : bool + If False, the simulation will run at the time step defined by the controller + sampling rate. Be aware that this will make the simulation run much slower. """ def __init__( @@ -39,6 +42,7 @@ def __init__( heading=None, initial_solution=None, terminate_on_apogee=None, + time_overshoot=None, ): """Initializes the Stochastic Flight class. @@ -63,11 +67,18 @@ def __init__( terminate_on_apogee : bool, optional Whether or not the flight should terminate on apogee. This attribute can not be randomized. + time_overshoot : bool + If False, the simulation will run at the time step defined by the controller + sampling rate. Be aware that this will make the simulation run much slower. """ if terminate_on_apogee is not None: assert isinstance(terminate_on_apogee, bool), ( "`terminate_on_apogee` must be a boolean" ) + if time_overshoot is not None: + assert isinstance(time_overshoot, bool), ( + "`time_overshoot` must be a boolean" + ) super().__init__( flight, rail_length=rail_length, @@ -77,6 +88,7 @@ def __init__( self.initial_solution = initial_solution self.terminate_on_apogee = terminate_on_apogee + self.time_overshoot = time_overshoot def _validate_initial_solution(self, initial_solution): if initial_solution is not None: @@ -128,4 +140,5 @@ def create_object(self): heading=generated_dict["heading"], initial_solution=self.initial_solution, terminate_on_apogee=self.terminate_on_apogee, + time_overshoot=self.time_overshoot, ) From 7f082d2cbcba9a8342b4e5324b99f86b999bd68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 17:35:52 +0100 Subject: [PATCH 11/19] DOC: StochasticAirBrakes related documentation added Documentation related to the StochasticAirBrakes implementation has been added in StochasticAirBrakes, StochasticRocket and Rocket classes. --- rocketpy/rocket/rocket.py | 16 +++++++++++-- .../stochastic/stochastic_aero_surfaces.py | 24 +++++++++++++++++-- rocketpy/stochastic/stochastic_rocket.py | 10 +++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index e40b71a9c..520747f77 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1539,9 +1539,21 @@ def set_air_brakes( air_brakes, controller, ): - + """Adds an air brake with a controller to the rocket. + + Parameters + ---------- + air_brakes : AirBrakes + Air brake to be added to the rocket. + controller : _Controller + Air brake controller + + Returns + ------- + None + """ self.air_brakes.append(air_brakes) - self._add_controllers(controller) + self._add_controllers(controller) def add_air_brakes( self, diff --git a/rocketpy/stochastic/stochastic_aero_surfaces.py b/rocketpy/stochastic/stochastic_aero_surfaces.py index 17f22337b..4241130c8 100644 --- a/rocketpy/stochastic/stochastic_aero_surfaces.py +++ b/rocketpy/stochastic/stochastic_aero_surfaces.py @@ -447,7 +447,7 @@ class StochasticAirBrakes(StochasticModel): ---------- object : AirBrakes AirBrakes object to be used for validation. - drag_coefficient_curve : list + drag_coefficient_curve : list, str The drag coefficient curve of the air brakes can account for either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. @@ -455,6 +455,16 @@ class StochasticAirBrakes(StochasticModel): The drag curve factor of the air brakes. reference_area : tuple, list, int, float Reference area used to non-dimensionalize the drag coefficients. + clamp : bool + If True, the simulation will clamp the deployment level to 0 or 1 if + the deployment level is out of bounds. If False, the simulation will + not clamp the deployment level and will instead raise a warning if + the deployment level is out of bounds. + override_rocket_drag : bool + If False, the air brakes drag coefficient will be added to the + rocket's power off drag coefficient curve. If True, during the + simulation, the rocket's power off drag will be ignored and the air + brakes drag coefficient will be used for the entire rocket instead. deployment_level : tuple, list, int, float Initial deployment level, ranging from 0 to 1. name : list[str] @@ -481,7 +491,7 @@ def __init__( ---------- air_brakes : AirBrakes AirBrakes object to be used for validation. - drag_coefficient_curve : list, optional + drag_coefficient_curve : list, str, optional The drag coefficient curve of the air brakes can account for either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. @@ -489,6 +499,16 @@ def __init__( The drag curve factor of the air brakes. reference_area : tuple, list, int, float, optional Reference area used to non-dimensionalize the drag coefficients. + clamp : bool, optional + If True, the simulation will clamp the deployment level to 0 or 1 if + the deployment level is out of bounds. If False, the simulation will + not clamp the deployment level and will instead raise a warning if + the deployment level is out of bounds. + override_rocket_drag : bool, optional + If False, the air brakes drag coefficient will be added to the + rocket's power off drag coefficient curve. If True, during the + simulation, the rocket's power off drag will be ignored and the air + brakes drag coefficient will be used for the entire rocket instead. deployment_level : tuple, list, int, float, optional Initial deployment level, ranging from 0 to 1. """ diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 399ea568d..3e29e5963 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -398,7 +398,15 @@ def add_air_brakes( air_brakes, controller ): - + """Adds an air brake to the stochastic rocket. + + Parameters + ---------- + air_brakes : StochasticAirBrakes or Airbrakes + The air brake to be added to the stochastic rocket. + controller : _Controller + Deterministic air brake controller. + """ # checks if input is a StochasticAirbrakes type if not isinstance(air_brakes, (AirBrakes, StochasticAirBrakes)): raise TypeError( From 1c4c79122958e9fc994cae904fa10efc28c8cfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 23:00:00 +0100 Subject: [PATCH 12/19] ENH: pylint recommendations done --- rocketpy/mathutils/function.py | 2 +- rocketpy/stochastic/stochastic_aero_surfaces.py | 4 ++-- rocketpy/stochastic/stochastic_rocket.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 869f5a7b9..209544b10 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -2219,7 +2219,7 @@ def __rsub__(self, other): """ return other + (-self) - def __mul__(self, other): + def __mul__(self, other): # pylint: disable=too-many-statements """Multiplies a Function object and returns a new Function object which gives the result of the multiplication. Only implemented for 1D and 2D domains. diff --git a/rocketpy/stochastic/stochastic_aero_surfaces.py b/rocketpy/stochastic/stochastic_aero_surfaces.py index 4241130c8..e9745ac01 100644 --- a/rocketpy/stochastic/stochastic_aero_surfaces.py +++ b/rocketpy/stochastic/stochastic_aero_surfaces.py @@ -433,7 +433,7 @@ def create_object(self): """ generated_dict = next(self.dict_generator()) return RailButtons(**generated_dict) - + class StochasticAirBrakes(StochasticModel): """A Stochastic Air Brakes class that inherits from StochasticModel. @@ -541,4 +541,4 @@ def create_object(self): deployment_level=generated_dict['deployment_level'], ) air_brakes.drag_coefficient *= generated_dict['drag_coefficient_curve_factor'] - return air_brakes + return air_brakes diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 3e29e5963..6dcc7963a 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -602,7 +602,7 @@ def _create_rail_buttons(self, component_stochastic_rail_buttons): upper_button_position_rnd ) return rail_buttons, lower_button_position_rnd, upper_button_position_rnd - + def _create_air_brake(self, stochastic_air_brake): air_brake = stochastic_air_brake.create_object() self.last_rnd_dict["air_brakes"].append(stochastic_air_brake.last_rnd_dict) From 85e82e639931e45b6bac1d35484a93e64368d6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Tue, 18 Mar 2025 23:11:33 +0100 Subject: [PATCH 13/19] ENH: Reformatted files to pass Ruff linting checks --- rocketpy/mathutils/function.py | 20 ++++++++++++++----- .../stochastic/stochastic_aero_surfaces.py | 18 ++++++++--------- rocketpy/stochastic/stochastic_rocket.py | 6 +----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 209544b10..aa717b2c3 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -2264,7 +2264,9 @@ def __mul__(self, other): # pylint: disable=too-many-statements ): if not self_source_is_array: return Function(lambda x: (self.get_value_opt(x) * other), inputs) - source = np.column_stack((self.x_array, np.multiply(self.y_array, other))) + source = np.column_stack( + (self.x_array, np.multiply(self.y_array, other)) + ) outputs = f"({self.__outputs__[0]}*{other})" return Function( source, @@ -2284,15 +2286,21 @@ def __mul__(self, other): # pylint: disable=too-many-statements and np.array_equal(self.x_array, other.x_array) and np.array_equal(self.y_array, other.y_array) ): - source = np.column_stack((self.x_array, self.y_array, self.z_array * other.z_array)) + source = np.column_stack( + (self.x_array, self.y_array, self.z_array * other.z_array) + ) outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})" return Function(source, inputs, outputs, interp, extrap) elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array( other ): if not self_source_is_array: - return Function(lambda x,y: (self.get_value_opt(x,y) * other), inputs) - source = np.column_stack((self.x_array, self.y_array, np.multiply(self.z_array, other))) + return Function( + lambda x, y: (self.get_value_opt(x, y) * other), inputs + ) + source = np.column_stack( + (self.x_array, self.y_array, np.multiply(self.z_array, other)) + ) outputs = f"({self.__outputs__[0]}*{other})" return Function( source, @@ -2302,7 +2310,9 @@ def __mul__(self, other): # pylint: disable=too-many-statements extrap, ) elif callable(other): - return Function(lambda x,y: (self.get_value_opt(x,y) * other(x)), inputs) + return Function( + lambda x, y: (self.get_value_opt(x, y) * other(x)), inputs + ) else: raise TypeError("Unsupported type for multiplication") diff --git a/rocketpy/stochastic/stochastic_aero_surfaces.py b/rocketpy/stochastic/stochastic_aero_surfaces.py index e9745ac01..863c97d64 100644 --- a/rocketpy/stochastic/stochastic_aero_surfaces.py +++ b/rocketpy/stochastic/stochastic_aero_surfaces.py @@ -448,7 +448,7 @@ class StochasticAirBrakes(StochasticModel): object : AirBrakes AirBrakes object to be used for validation. drag_coefficient_curve : list, str - The drag coefficient curve of the air brakes can account for + The drag coefficient curve of the air brakes can account for either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. drag_coefficient_curve_factor : tuple, list, int, float @@ -479,7 +479,7 @@ def __init__( reference_area=None, clamp=None, override_rocket_drag=None, - deployment_level=(0,0), + deployment_level=(0, 0), ): """Initializes the Stochastic AirBrakes class. @@ -492,7 +492,7 @@ def __init__( air_brakes : AirBrakes AirBrakes object to be used for validation. drag_coefficient_curve : list, str, optional - The drag coefficient curve of the air brakes can account for + The drag coefficient curve of the air brakes can account for either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. drag_coefficient_curve_factor : tuple, list, int, float, optional @@ -534,11 +534,11 @@ def create_object(self): """ generated_dict = next(self.dict_generator()) air_brakes = AirBrakes( - drag_coefficient_curve=generated_dict['drag_coefficient_curve'], - reference_area=generated_dict['reference_area'], - clamp=generated_dict['clamp'], - override_rocket_drag=generated_dict['override_rocket_drag'], - deployment_level=generated_dict['deployment_level'], + drag_coefficient_curve=generated_dict["drag_coefficient_curve"], + reference_area=generated_dict["reference_area"], + clamp=generated_dict["clamp"], + override_rocket_drag=generated_dict["override_rocket_drag"], + deployment_level=generated_dict["deployment_level"], ) - air_brakes.drag_coefficient *= generated_dict['drag_coefficient_curve_factor'] + air_brakes.drag_coefficient *= generated_dict["drag_coefficient_curve_factor"] return air_brakes diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 6dcc7963a..3a298dd61 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -393,11 +393,7 @@ def set_rail_buttons( rail_buttons, self._validate_position(rail_buttons, lower_button_position) ) - def add_air_brakes( - self, - air_brakes, - controller - ): + def add_air_brakes(self, air_brakes, controller): """Adds an air brake to the stochastic rocket. Parameters From a52ad3e3f3dc43ce3f87e69d6f75e21ce98b9ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Wed, 19 Mar 2025 21:52:56 +0100 Subject: [PATCH 14/19] ENH: Update rocketpy/stochastic/stochastic_rocket.py Unnecessary comment Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/stochastic/stochastic_rocket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 3a298dd61..67dd4cc8d 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -403,7 +403,6 @@ def add_air_brakes(self, air_brakes, controller): controller : _Controller Deterministic air brake controller. """ - # checks if input is a StochasticAirbrakes type if not isinstance(air_brakes, (AirBrakes, StochasticAirBrakes)): raise TypeError( "`air_brake` must be of AirBrakes or StochasticAirBrakes type" From 91f83c89fed3d90b7b5993f5ac8cb124fcb8bfc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 23 Mar 2025 11:38:14 +0100 Subject: [PATCH 15/19] DOC: improve drag curve factor definition in StochasticAirBrakes --- rocketpy/stochastic/stochastic_aero_surfaces.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rocketpy/stochastic/stochastic_aero_surfaces.py b/rocketpy/stochastic/stochastic_aero_surfaces.py index 863c97d64..5dda716bb 100644 --- a/rocketpy/stochastic/stochastic_aero_surfaces.py +++ b/rocketpy/stochastic/stochastic_aero_surfaces.py @@ -452,7 +452,8 @@ class StochasticAirBrakes(StochasticModel): either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. drag_coefficient_curve_factor : tuple, list, int, float - The drag curve factor of the air brakes. + The drag curve factor of the air brakes. This value scales the + drag coefficient curve to introduce stochastic variability. reference_area : tuple, list, int, float Reference area used to non-dimensionalize the drag coefficients. clamp : bool @@ -496,7 +497,8 @@ def __init__( either the air brakes' drag alone or the combined drag of both the rocket and the air brakes. drag_coefficient_curve_factor : tuple, list, int, float, optional - The drag curve factor of the air brakes. + The drag curve factor of the air brakes. This value scales the + drag coefficient curve to introduce stochastic variability. reference_area : tuple, list, int, float, optional Reference area used to non-dimensionalize the drag coefficients. clamp : bool, optional From 95a69faa7996dc9006fd73fd2d4542910647b252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 23 Mar 2025 11:52:11 +0100 Subject: [PATCH 16/19] ENH: Change assert statement to if Co-authored-by: Gui-FernandesBR <63590233+Gui-FernandesBR@users.noreply.github.com> --- rocketpy/stochastic/stochastic_flight.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index ed9037088..729313736 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -76,9 +76,8 @@ def __init__( "`terminate_on_apogee` must be a boolean" ) if time_overshoot is not None: - assert isinstance(time_overshoot, bool), ( - "`time_overshoot` must be a boolean" - ) + if not isinstance(time_overshoot, bool): + raise TypeError("`time_overshoot` must be a boolean") super().__init__( flight, rail_length=rail_length, From ac958d637db35acf6a30c5167a3f2f73ed67ae8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 23 Mar 2025 19:38:52 +0100 Subject: [PATCH 17/19] DOC: better explanation of __mul__ function Co-authored-by: MateusStano <69485049+MateusStano@users.noreply.github.com> --- rocketpy/mathutils/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index aa717b2c3..c7d11910b 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -2238,7 +2238,7 @@ def __mul__(self, other): # pylint: disable=too-many-statements Returns ------- result : Function - A Function object which gives the result of self(x,y)*other(x,y). + A Function object which gives the result of self(x)*other(x) or self(x,y)*other(x,y). """ self_source_is_array = isinstance(self.source, np.ndarray) other_source_is_array = ( From d51f15b3cb163eb79881d2a4fbaedf7711edc05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 23 Mar 2025 20:12:30 +0100 Subject: [PATCH 18/19] ENH: delete set_air_brakes function for simplicity --- rocketpy/rocket/rocket.py | 21 --------------------- rocketpy/stochastic/stochastic_rocket.py | 6 ++---- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 520747f77..609e1b76c 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1534,27 +1534,6 @@ def add_sensor(self, sensor, position): except KeyError: sensor._attached_rockets[self] = 1 - def set_air_brakes( - self, - air_brakes, - controller, - ): - """Adds an air brake with a controller to the rocket. - - Parameters - ---------- - air_brakes : AirBrakes - Air brake to be added to the rocket. - controller : _Controller - Air brake controller - - Returns - ------- - None - """ - self.air_brakes.append(air_brakes) - self._add_controllers(controller) - def add_air_brakes( self, drag_coefficient_curve, diff --git a/rocketpy/stochastic/stochastic_rocket.py b/rocketpy/stochastic/stochastic_rocket.py index 67dd4cc8d..718176e36 100644 --- a/rocketpy/stochastic/stochastic_rocket.py +++ b/rocketpy/stochastic/stochastic_rocket.py @@ -655,10 +655,8 @@ def create_object(self): sampling_rate=self.air_brake_controller.sampling_rate, initial_observed_variables=self.air_brake_controller.initial_observed_variables, ) - rocket.set_air_brakes( - air_brakes=air_brake, - controller=_controller, - ) + rocket.air_brakes.append(air_brake) + rocket._add_controllers(_controller) for component_rail_buttons in self.rail_buttons: ( From f83bf877edec43a2b48330bed4ce7dcd4e293b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Alca=C3=B1iz?= Date: Sun, 23 Mar 2025 21:38:18 +0100 Subject: [PATCH 19/19] DOC: CHANGELOG file updated --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11912d29a..9a0d664c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ Attention: The newest changes should be on top --> - ENH: Parallel mode for monte-carlo simulations 2 [#768](https://github.com/RocketPy-Team/RocketPy/pull/768) - DOC: ASTRA Flight Example [#770](https://github.com/RocketPy-Team/RocketPy/pull/770) +- ENH: Add Eccentricity to Stochastic Simulations [#792](https://github.com/RocketPy-Team/RocketPy/pull/792) +- ENH: Introduce the StochasticAirBrakes class [#785](https://github.com/RocketPy-Team/RocketPy/pull/785) ### Changed @@ -46,6 +48,7 @@ Attention: The newest changes should be on top --> - BUG: update flight simulation logic to include burn start time [#778](https://github.com/RocketPy-Team/RocketPy/pull/778) - BUG: fixes get_instance_attributes for Flight objects containing a Rocket object without rail buttons [#786](https://github.com/RocketPy-Team/RocketPy/pull/786) - BUG: fixed AGL altitude print for parachutes with lag [#788](https://github.com/RocketPy-Team/RocketPy/pull/788) +- BUG: fix the wind velocity factors usage and better visualization of uniform distributions in Stochastic Classes [#783](https://github.com/RocketPy-Team/RocketPy/pull/783) ## [v1.8.0] - 2025-01-20