Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9fe3a37
wind factor bug corrected
kevin-alcaniz Mar 14, 2025
f6efa81
BUG: StochasticModel visualize attributes of a uniform distribution
kevin-alcaniz Mar 15, 2025
04337ab
variable names corrections
kevin-alcaniz Mar 15, 2025
7f551c2
Merge branch 'develop' into develop
kevin-alcaniz Mar 15, 2025
18f8323
Corrections requested by the pylint test
kevin-alcaniz Mar 15, 2025
72c63b4
Merge branch 'develop' of https://github.com/kevin-alcaniz/RocketPy i…
kevin-alcaniz Mar 15, 2025
a46de46
ENH: add multiplication for 2D functions in rocketpy.function
kevin-alcaniz Mar 16, 2025
7181360
ENH: StochasticAirBrakes class created
kevin-alcaniz Mar 16, 2025
e1549e0
ENH: set_air_brakes function created
kevin-alcaniz Mar 18, 2025
89a83a6
Merge pull request #1 from kevin-alcaniz/enh/set-air-brakes-function
kevin-alcaniz Mar 18, 2025
b14ce09
Merge pull request #2 from kevin-alcaniz/enh/multiply_2D_functions
kevin-alcaniz Mar 18, 2025
2d2a0e8
ENH: add StochasticAirBrake to rocketpy.stochastic_rocket
kevin-alcaniz Mar 18, 2025
b206647
BUG: StochasticAirBrake object input in _Controller
kevin-alcaniz Mar 18, 2025
f0ebdda
Merge pull request #3 from kevin-alcaniz/bug/stochastic-airbrake-in-c…
kevin-alcaniz Mar 18, 2025
e45ae76
ENH: add time_overshoot option to rocketpy.stochastic_flight
kevin-alcaniz Mar 18, 2025
7f082d2
DOC: StochasticAirBrakes related documentation added
kevin-alcaniz Mar 18, 2025
1c4c791
ENH: pylint recommendations done
kevin-alcaniz Mar 18, 2025
85e82e6
ENH: Reformatted files to pass Ruff linting checks
kevin-alcaniz Mar 18, 2025
0eead44
Merge pull request #4 from kevin-alcaniz/enh/ruff-modifications
kevin-alcaniz Mar 18, 2025
a52ad3e
ENH: Update rocketpy/stochastic/stochastic_rocket.py
kevin-alcaniz Mar 19, 2025
91f83c8
DOC: improve drag curve factor definition in StochasticAirBrakes
kevin-alcaniz Mar 23, 2025
95a69fa
ENH: Change assert statement to if
kevin-alcaniz Mar 23, 2025
ac958d6
DOC: better explanation of __mul__ function
kevin-alcaniz Mar 23, 2025
d51f15b
ENH: delete set_air_brakes function for simplicity
kevin-alcaniz Mar 23, 2025
8e66319
Merge branch 'enh/stochastic-airbrakes-feature' of https://github.com…
kevin-alcaniz Mar 23, 2025
6d8c9b5
Merge branch 'develop' into enh/stochastic-airbrakes-feature
kevin-alcaniz Mar 23, 2025
f83bf87
DOC: CHANGELOG file updated
kevin-alcaniz Mar 23, 2025
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
1 change: 1 addition & 0 deletions rocketpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope
from .simulation import Flight, MonteCarlo
from .stochastic import (
StochasticAirBrakes,
StochasticEllipticalFins,
StochasticEnvironment,
StochasticFlight,
Expand Down
101 changes: 70 additions & 31 deletions rocketpy/mathutils/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -2219,10 +2219,10 @@ 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
domains.
and 2D domains.

Parameters
----------
Expand All @@ -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)*other(x) or self(x,y)*other(x,y).
"""
self_source_is_array = isinstance(self.source, np.ndarray)
other_source_is_array = (
Expand All @@ -2250,37 +2250,76 @@ 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
----------
Expand All @@ -2290,7 +2329,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

Expand Down
1 change: 1 addition & 0 deletions rocketpy/simulation/monte_carlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions rocketpy/stochastic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

from .stochastic_aero_surfaces import (
StochasticAirBrakes,
StochasticEllipticalFins,
StochasticNoseCone,
StochasticRailButtons,
Expand Down
112 changes: 112 additions & 0 deletions rocketpy/stochastic/stochastic_aero_surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
RailButtons,
Tail,
TrapezoidalFins,
AirBrakes,
)

from .stochastic_model import StochasticModel
Expand Down Expand Up @@ -432,3 +433,114 @@ 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 <rocketpy.AirBrakes>`

Attributes
----------
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
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. 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
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]
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, 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.
drag_coefficient_curve_factor : tuple, list, int, float, optional
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
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.
"""
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
1 change: 1 addition & 0 deletions rocketpy/stochastic/stochastic_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions rocketpy/stochastic/stochastic_flight.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand All @@ -39,6 +42,7 @@ def __init__(
heading=None,
initial_solution=None,
terminate_on_apogee=None,
time_overshoot=None,
):
"""Initializes the Stochastic Flight class.

Expand All @@ -63,11 +67,17 @@ 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:
if not isinstance(time_overshoot, bool):
raise TypeError("`time_overshoot` must be a boolean")
super().__init__(
flight,
rail_length=rail_length,
Expand All @@ -77,6 +87,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:
Expand Down Expand Up @@ -128,4 +139,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,
)
19 changes: 14 additions & 5 deletions rocketpy/stochastic/stochastic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,11 +514,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 callable(dist_func) and dist_func.__name__ == "uniform":
mean = (std_dev + nominal_value) / 2
half_range = (std_dev - nominal_value) / 2
return (
f"\t{attr.ljust(max_str_length)} "
f"{mean:.5f} ± "
f"{half_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("_")}
Expand Down
Loading