Skip to content

Commit 4a41f7a

Browse files
kevin-alcanizGui-FernandesBRMateusStano
authored
ENH: Introduce the StochasticAirBrakes class (#785)
* wind factor bug corrected the wind factor wasn't applied to the env.wind_velocity properties * 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 * variable names corrections * Corrections requested by the pylint test * ENH: add multiplication for 2D functions in rocketpy.function Added the ability to multiply functions with 2D domains in the __mul__ function * 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 * ENH: set_air_brakes function created This functions appends an airbrake and controller objects previuosly created to the rocket * 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 * 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 * 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. * DOC: StochasticAirBrakes related documentation added Documentation related to the StochasticAirBrakes implementation has been added in StochasticAirBrakes, StochasticRocket and Rocket classes. * ENH: pylint recommendations done * ENH: Reformatted files to pass Ruff linting checks * ENH: Update rocketpy/stochastic/stochastic_rocket.py Unnecessary comment Co-authored-by: Gui-FernandesBR <[email protected]> * DOC: improve drag curve factor definition in StochasticAirBrakes * ENH: Change assert statement to if Co-authored-by: Gui-FernandesBR <[email protected]> * DOC: better explanation of __mul__ function Co-authored-by: MateusStano <[email protected]> * ENH: delete set_air_brakes function for simplicity * DOC: CHANGELOG file updated --------- Co-authored-by: Gui-FernandesBR <[email protected]> Co-authored-by: MateusStano <[email protected]>
1 parent 90553f5 commit 4a41f7a

File tree

8 files changed

+241
-31
lines changed

8 files changed

+241
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Attention: The newest changes should be on top -->
3434

3535
- ENH: Parallel mode for monte-carlo simulations 2 [#768](https://github.com/RocketPy-Team/RocketPy/pull/768)
3636
- DOC: ASTRA Flight Example [#770](https://github.com/RocketPy-Team/RocketPy/pull/770)
37+
- ENH: Add Eccentricity to Stochastic Simulations [#792](https://github.com/RocketPy-Team/RocketPy/pull/792)
38+
- ENH: Introduce the StochasticAirBrakes class [#785](https://github.com/RocketPy-Team/RocketPy/pull/785)
3739

3840
### Changed
3941

@@ -46,6 +48,7 @@ Attention: The newest changes should be on top -->
4648
- BUG: update flight simulation logic to include burn start time [#778](https://github.com/RocketPy-Team/RocketPy/pull/778)
4749
- BUG: fixes get_instance_attributes for Flight objects containing a Rocket object without rail buttons [#786](https://github.com/RocketPy-Team/RocketPy/pull/786)
4850
- BUG: fixed AGL altitude print for parachutes with lag [#788](https://github.com/RocketPy-Team/RocketPy/pull/788)
51+
- 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)
4952

5053

5154
## [v1.8.0] - 2025-01-20

rocketpy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope
4545
from .simulation import Flight, MonteCarlo
4646
from .stochastic import (
47+
StochasticAirBrakes,
4748
StochasticEllipticalFins,
4849
StochasticEnvironment,
4950
StochasticFlight,

rocketpy/mathutils/function.py

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,10 +2219,10 @@ def __rsub__(self, other):
22192219
"""
22202220
return other + (-self)
22212221

2222-
def __mul__(self, other):
2222+
def __mul__(self, other): # pylint: disable=too-many-statements
22232223
"""Multiplies a Function object and returns a new Function object
22242224
which gives the result of the multiplication. Only implemented for 1D
2225-
domains.
2225+
and 2D domains.
22262226
22272227
Parameters
22282228
----------
@@ -2238,7 +2238,7 @@ def __mul__(self, other):
22382238
Returns
22392239
-------
22402240
result : Function
2241-
A Function object which gives the result of self(x)*other(x).
2241+
A Function object which gives the result of self(x)*other(x) or self(x,y)*other(x,y).
22422242
"""
22432243
self_source_is_array = isinstance(self.source, np.ndarray)
22442244
other_source_is_array = (
@@ -2250,37 +2250,76 @@ def __mul__(self, other):
22502250
interp = self.__interpolation__
22512251
extrap = self.__extrapolation__
22522252

2253-
if (
2254-
self_source_is_array
2255-
and other_source_is_array
2256-
and np.array_equal(self.x_array, other.x_array)
2257-
):
2258-
source = np.column_stack((self.x_array, self.y_array * other.y_array))
2259-
outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})"
2260-
return Function(source, inputs, outputs, interp, extrap)
2261-
elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array(
2262-
other
2263-
):
2264-
if not self_source_is_array:
2265-
return Function(lambda x: (self.get_value_opt(x) * other), inputs)
2266-
source = np.column_stack((self.x_array, np.multiply(self.y_array, other)))
2267-
outputs = f"({self.__outputs__[0]}*{other})"
2268-
return Function(
2269-
source,
2270-
inputs,
2271-
outputs,
2272-
interp,
2273-
extrap,
2274-
)
2275-
elif callable(other):
2276-
return Function(lambda x: (self.get_value_opt(x) * other(x)), inputs)
2277-
else:
2278-
raise TypeError("Unsupported type for multiplication")
2253+
if self.__dom_dim__ == 1:
2254+
if (
2255+
self_source_is_array
2256+
and other_source_is_array
2257+
and np.array_equal(self.x_array, other.x_array)
2258+
):
2259+
source = np.column_stack((self.x_array, self.y_array * other.y_array))
2260+
outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})"
2261+
return Function(source, inputs, outputs, interp, extrap)
2262+
elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array(
2263+
other
2264+
):
2265+
if not self_source_is_array:
2266+
return Function(lambda x: (self.get_value_opt(x) * other), inputs)
2267+
source = np.column_stack(
2268+
(self.x_array, np.multiply(self.y_array, other))
2269+
)
2270+
outputs = f"({self.__outputs__[0]}*{other})"
2271+
return Function(
2272+
source,
2273+
inputs,
2274+
outputs,
2275+
interp,
2276+
extrap,
2277+
)
2278+
elif callable(other):
2279+
return Function(lambda x: (self.get_value_opt(x) * other(x)), inputs)
2280+
else:
2281+
raise TypeError("Unsupported type for multiplication")
2282+
elif self.__dom_dim__ == 2:
2283+
if (
2284+
self_source_is_array
2285+
and other_source_is_array
2286+
and np.array_equal(self.x_array, other.x_array)
2287+
and np.array_equal(self.y_array, other.y_array)
2288+
):
2289+
source = np.column_stack(
2290+
(self.x_array, self.y_array, self.z_array * other.z_array)
2291+
)
2292+
outputs = f"({self.__outputs__[0]}*{other.__outputs__[0]})"
2293+
return Function(source, inputs, outputs, interp, extrap)
2294+
elif isinstance(other, NUMERICAL_TYPES) or self.__is_single_element_array(
2295+
other
2296+
):
2297+
if not self_source_is_array:
2298+
return Function(
2299+
lambda x, y: (self.get_value_opt(x, y) * other), inputs
2300+
)
2301+
source = np.column_stack(
2302+
(self.x_array, self.y_array, np.multiply(self.z_array, other))
2303+
)
2304+
outputs = f"({self.__outputs__[0]}*{other})"
2305+
return Function(
2306+
source,
2307+
inputs,
2308+
outputs,
2309+
interp,
2310+
extrap,
2311+
)
2312+
elif callable(other):
2313+
return Function(
2314+
lambda x, y: (self.get_value_opt(x, y) * other(x)), inputs
2315+
)
2316+
else:
2317+
raise TypeError("Unsupported type for multiplication")
22792318

22802319
def __rmul__(self, other):
22812320
"""Multiplies 'other' by a Function object and returns a new Function
22822321
object which gives the result of the multiplication. Only implemented for
2283-
1D domains.
2322+
1D and 2D domains.
22842323
22852324
Parameters
22862325
----------
@@ -2290,7 +2329,7 @@ def __rmul__(self, other):
22902329
Returns
22912330
-------
22922331
result : Function
2293-
A Function object which gives the result of other(x)*self(x).
2332+
A Function object which gives the result of other(x,y)*self(x,y).
22942333
"""
22952334
return self * other
22962335

rocketpy/simulation/monte_carlo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ def __run_single_simulation(self):
458458
heading=self.flight._randomize_heading(),
459459
initial_solution=self.flight.initial_solution,
460460
terminate_on_apogee=self.flight.terminate_on_apogee,
461+
time_overshoot=self.flight.time_overshoot,
461462
)
462463

463464
def __evaluate_flight_inputs(self, sim_idx):

rocketpy/stochastic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
from .stochastic_aero_surfaces import (
9+
StochasticAirBrakes,
910
StochasticEllipticalFins,
1011
StochasticNoseCone,
1112
StochasticRailButtons,

rocketpy/stochastic/stochastic_aero_surfaces.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
RailButtons,
1010
Tail,
1111
TrapezoidalFins,
12+
AirBrakes,
1213
)
1314

1415
from .stochastic_model import StochasticModel
@@ -432,3 +433,114 @@ def create_object(self):
432433
"""
433434
generated_dict = next(self.dict_generator())
434435
return RailButtons(**generated_dict)
436+
437+
438+
class StochasticAirBrakes(StochasticModel):
439+
"""A Stochastic Air Brakes class that inherits from StochasticModel.
440+
441+
See Also
442+
--------
443+
:ref:`stochastic_model` and
444+
:class:`AirBrakes <rocketpy.AirBrakes>`
445+
446+
Attributes
447+
----------
448+
object : AirBrakes
449+
AirBrakes object to be used for validation.
450+
drag_coefficient_curve : list, str
451+
The drag coefficient curve of the air brakes can account for
452+
either the air brakes' drag alone or the combined drag of both
453+
the rocket and the air brakes.
454+
drag_coefficient_curve_factor : tuple, list, int, float
455+
The drag curve factor of the air brakes. This value scales the
456+
drag coefficient curve to introduce stochastic variability.
457+
reference_area : tuple, list, int, float
458+
Reference area used to non-dimensionalize the drag coefficients.
459+
clamp : bool
460+
If True, the simulation will clamp the deployment level to 0 or 1 if
461+
the deployment level is out of bounds. If False, the simulation will
462+
not clamp the deployment level and will instead raise a warning if
463+
the deployment level is out of bounds.
464+
override_rocket_drag : bool
465+
If False, the air brakes drag coefficient will be added to the
466+
rocket's power off drag coefficient curve. If True, during the
467+
simulation, the rocket's power off drag will be ignored and the air
468+
brakes drag coefficient will be used for the entire rocket instead.
469+
deployment_level : tuple, list, int, float
470+
Initial deployment level, ranging from 0 to 1.
471+
name : list[str]
472+
List with the air brakes object name. This attribute can't be randomized.
473+
"""
474+
475+
def __init__(
476+
self,
477+
air_brakes,
478+
drag_coefficient_curve=None,
479+
drag_coefficient_curve_factor=(1, 0),
480+
reference_area=None,
481+
clamp=None,
482+
override_rocket_drag=None,
483+
deployment_level=(0, 0),
484+
):
485+
"""Initializes the Stochastic AirBrakes class.
486+
487+
See Also
488+
--------
489+
:ref:`stochastic_model`
490+
491+
Parameters
492+
----------
493+
air_brakes : AirBrakes
494+
AirBrakes object to be used for validation.
495+
drag_coefficient_curve : list, str, optional
496+
The drag coefficient curve of the air brakes can account for
497+
either the air brakes' drag alone or the combined drag of both
498+
the rocket and the air brakes.
499+
drag_coefficient_curve_factor : tuple, list, int, float, optional
500+
The drag curve factor of the air brakes. This value scales the
501+
drag coefficient curve to introduce stochastic variability.
502+
reference_area : tuple, list, int, float, optional
503+
Reference area used to non-dimensionalize the drag coefficients.
504+
clamp : bool, optional
505+
If True, the simulation will clamp the deployment level to 0 or 1 if
506+
the deployment level is out of bounds. If False, the simulation will
507+
not clamp the deployment level and will instead raise a warning if
508+
the deployment level is out of bounds.
509+
override_rocket_drag : bool, optional
510+
If False, the air brakes drag coefficient will be added to the
511+
rocket's power off drag coefficient curve. If True, during the
512+
simulation, the rocket's power off drag will be ignored and the air
513+
brakes drag coefficient will be used for the entire rocket instead.
514+
deployment_level : tuple, list, int, float, optional
515+
Initial deployment level, ranging from 0 to 1.
516+
"""
517+
super().__init__(
518+
air_brakes,
519+
drag_coefficient_curve=drag_coefficient_curve,
520+
drag_coefficient_curve_factor=drag_coefficient_curve_factor,
521+
reference_area=reference_area,
522+
clamp=clamp,
523+
override_rocket_drag=override_rocket_drag,
524+
deployment_level=deployment_level,
525+
name=None,
526+
)
527+
528+
def create_object(self):
529+
"""Creates and returns an AirBrakes object from the randomly generated
530+
input arguments.
531+
532+
Returns
533+
-------
534+
air_brake : AirBrakes
535+
AirBrakes object with the randomly generated input arguments.
536+
"""
537+
generated_dict = next(self.dict_generator())
538+
air_brakes = AirBrakes(
539+
drag_coefficient_curve=generated_dict["drag_coefficient_curve"],
540+
reference_area=generated_dict["reference_area"],
541+
clamp=generated_dict["clamp"],
542+
override_rocket_drag=generated_dict["override_rocket_drag"],
543+
deployment_level=generated_dict["deployment_level"],
544+
)
545+
air_brakes.drag_coefficient *= generated_dict["drag_coefficient_curve_factor"]
546+
return air_brakes

rocketpy/stochastic/stochastic_flight.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class StochasticFlight(StochasticModel):
2929
terminate_on_apogee : bool
3030
Whether or not the flight should terminate on apogee. This attribute
3131
can not be randomized.
32+
time_overshoot : bool
33+
If False, the simulation will run at the time step defined by the controller
34+
sampling rate. Be aware that this will make the simulation run much slower.
3235
"""
3336

3437
def __init__(
@@ -39,6 +42,7 @@ def __init__(
3942
heading=None,
4043
initial_solution=None,
4144
terminate_on_apogee=None,
45+
time_overshoot=None,
4246
):
4347
"""Initializes the Stochastic Flight class.
4448
@@ -63,11 +67,17 @@ def __init__(
6367
terminate_on_apogee : bool, optional
6468
Whether or not the flight should terminate on apogee. This attribute
6569
can not be randomized.
70+
time_overshoot : bool
71+
If False, the simulation will run at the time step defined by the controller
72+
sampling rate. Be aware that this will make the simulation run much slower.
6673
"""
6774
if terminate_on_apogee is not None:
6875
assert isinstance(terminate_on_apogee, bool), (
6976
"`terminate_on_apogee` must be a boolean"
7077
)
78+
if time_overshoot is not None:
79+
if not isinstance(time_overshoot, bool):
80+
raise TypeError("`time_overshoot` must be a boolean")
7181
super().__init__(
7282
flight,
7383
rail_length=rail_length,
@@ -77,6 +87,7 @@ def __init__(
7787

7888
self.initial_solution = initial_solution
7989
self.terminate_on_apogee = terminate_on_apogee
90+
self.time_overshoot = time_overshoot
8091

8192
def _validate_initial_solution(self, initial_solution):
8293
if initial_solution is not None:
@@ -128,4 +139,5 @@ def create_object(self):
128139
heading=generated_dict["heading"],
129140
initial_solution=self.initial_solution,
130141
terminate_on_apogee=self.terminate_on_apogee,
142+
time_overshoot=self.time_overshoot,
131143
)

0 commit comments

Comments
 (0)