From e3478b617cd7bda6fb99249448a65099521a22b2 Mon Sep 17 00:00:00 2001 From: zuorenchen Date: Sun, 19 Jul 2026 01:31:46 +0100 Subject: [PATCH 1/4] Add max_time option to stochastic_flight --- rocketpy/stochastic/stochastic_flight.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index 0fde38c3a..eddf70027 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -32,6 +32,9 @@ class StochasticFlight(StochasticModel): 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. + max_time : int, float + The maximum time of the flight simulation. If the flight simulation + reaches this time, it will terminate. This attribute can not be randomized. """ def __init__( @@ -43,6 +46,7 @@ def __init__( initial_solution=None, terminate_on_apogee=None, time_overshoot=None, + max_time=None, ): """Initializes the Stochastic Flight class. @@ -70,6 +74,9 @@ def __init__( 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. + max_time : int, float + The maximum time of the flight simulation. If the flight simulation + reaches this time, it will terminate. This attribute can not be randomized. """ if terminate_on_apogee is not None: assert isinstance(terminate_on_apogee, bool), ( @@ -78,6 +85,9 @@ def __init__( if time_overshoot is not None: if not isinstance(time_overshoot, bool): raise TypeError("`time_overshoot` must be a boolean") + if max_time is not None: + if not isinstance(max_time, (int, float)): + raise TypeError("`max_time` must be a number") super().__init__( flight, rail_length=rail_length, @@ -87,6 +97,10 @@ def __init__( self.initial_solution = initial_solution self.terminate_on_apogee = terminate_on_apogee + if max_time is None: + self.max_time = flight.max_time + else: + self.max_time = max_time if time_overshoot is None: self.time_overshoot = flight.time_overshoot else: @@ -143,4 +157,5 @@ def create_object(self): initial_solution=self.initial_solution, terminate_on_apogee=self.terminate_on_apogee, time_overshoot=self.time_overshoot, + max_time=self.max_time, ) From 04abba4c2ad0f97481b5352e6f5cfabb52059d04 Mon Sep 17 00:00:00 2001 From: zuorenchen Date: Sun, 19 Jul 2026 01:41:05 +0100 Subject: [PATCH 2/4] Update: use default attributes in flight object for stochastic flights --- rocketpy/stochastic/stochastic_flight.py | 43 ++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index eddf70027..abecb4be1 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -35,6 +35,22 @@ class StochasticFlight(StochasticModel): max_time : int, float The maximum time of the flight simulation. If the flight simulation reaches this time, it will terminate. This attribute can not be randomized. + max_time_step : + This attribute can not be randomized and is inherited from the base Flight object. + min_time_step : + This attribute can not be randomized and is inherited from the base Flight object. + rtol : + This attribute can not be randomized and is inherited from the base Flight object. + atol : + This attribute can not be randomized and is inherited from the base Flight object. + name : + This attribute can not be randomized and is inherited from the base Flight object. + equations_of_motion: + This attribute can not be randomized and is inherited from the base Flight object. + ode_solver: + This attribute can not be randomized and is inherited from the base Flight object. + simulation_mode: + This attribute can not be randomized and is inherited from the base Flight object. """ def __init__( @@ -106,6 +122,21 @@ def __init__( else: self.time_overshoot = time_overshoot + # The following attributes are not randomized and are set to the values + # of the flight object. Move to helper to reduce statements in __init__. + self._copy_nonrandom_attrs(flight) + + def _copy_nonrandom_attrs(self, flight): + """Copy non-randomized attributes from the base Flight object.""" + self.max_time_step = flight.max_time_step + self.min_time_step = flight.min_time_step + self.rtol = flight.rtol + self.atol = flight.atol + self.name = flight.name + self.equations_of_motion = flight.equations_of_motion + self.ode_solver = flight.ode_solver + self.simulation_mode = flight.simulation_mode + def _validate_initial_solution(self, initial_solution): if initial_solution is not None: if isinstance(initial_solution, (tuple, list)): @@ -149,13 +180,21 @@ def create_object(self): generated_dict = next(self.dict_generator()) # TODO: maybe we should use generated_dict["rail_length"] instead return Flight( + rocket=self.obj.rocket, environment=self.obj.env, rail_length=self._randomize_rail_length(), - rocket=self.obj.rocket, inclination=generated_dict["inclination"], heading=generated_dict["heading"], initial_solution=self.initial_solution, terminate_on_apogee=self.terminate_on_apogee, - time_overshoot=self.time_overshoot, max_time=self.max_time, + max_time_step=self.max_time_step, + min_time_step=self.min_time_step, + rtol=self.rtol, + atol=self.atol, + time_overshoot=self.time_overshoot, + name=self.name, + equations_of_motion=self.equations_of_motion, + ode_solver=self.ode_solver, + simulation_mode=self.simulation_mode, ) From c4802814b01377b1de918dc676f030ab06990bf2 Mon Sep 17 00:00:00 2001 From: zuorenchen Date: Sun, 19 Jul 2026 02:16:53 +0100 Subject: [PATCH 3/4] Add tests to check attributes --- .../unit/stochastic/test_stochastic_flight.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit/stochastic/test_stochastic_flight.py b/tests/unit/stochastic/test_stochastic_flight.py index aeb71b906..a5e0c5032 100644 --- a/tests/unit/stochastic/test_stochastic_flight.py +++ b/tests/unit/stochastic/test_stochastic_flight.py @@ -1,6 +1,34 @@ from rocketpy.simulation.flight import Flight +from rocketpy.stochastic import StochasticFlight def test_stochastic_flight_create_object(stochastic_flight): obj = stochastic_flight.create_object() assert isinstance(obj, Flight) + + +def test_stochastic_flight_default_attributes(stochastic_flight, flight_calisto_robust): + obj = stochastic_flight.create_object() + assert flight_calisto_robust.max_time_step == obj.max_time_step + assert flight_calisto_robust.min_time_step == obj.min_time_step + assert flight_calisto_robust.rtol == obj.rtol + assert flight_calisto_robust.atol == obj.atol + assert flight_calisto_robust.name == obj.name + assert flight_calisto_robust.equations_of_motion == obj.equations_of_motion + assert flight_calisto_robust.ode_solver == obj.ode_solver + assert flight_calisto_robust.simulation_mode == obj.simulation_mode + + +def test_stochastic_flight_optional_attributes(flight_calisto_robust): + stochastic_flight = StochasticFlight( + flight=flight_calisto_robust, + inclination=(84.7, 1), + heading=(53, 2), + terminate_on_apogee=True, + time_overshoot=True, + max_time=987.6, + ) + obj = stochastic_flight.create_object() + assert obj.terminate_on_apogee is True + assert obj.time_overshoot is True + assert obj.max_time == 987.6 From be7873247ca08309ab706691d0606bbcb53b6f1e Mon Sep 17 00:00:00 2001 From: zuorenchen Date: Sun, 19 Jul 2026 02:46:41 +0100 Subject: [PATCH 4/4] Refactor implementation --- rocketpy/stochastic/stochastic_flight.py | 47 ++++--------------- .../unit/stochastic/test_stochastic_flight.py | 35 +++++++++----- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/rocketpy/stochastic/stochastic_flight.py b/rocketpy/stochastic/stochastic_flight.py index abecb4be1..ecac053a3 100644 --- a/rocketpy/stochastic/stochastic_flight.py +++ b/rocketpy/stochastic/stochastic_flight.py @@ -35,22 +35,6 @@ class StochasticFlight(StochasticModel): max_time : int, float The maximum time of the flight simulation. If the flight simulation reaches this time, it will terminate. This attribute can not be randomized. - max_time_step : - This attribute can not be randomized and is inherited from the base Flight object. - min_time_step : - This attribute can not be randomized and is inherited from the base Flight object. - rtol : - This attribute can not be randomized and is inherited from the base Flight object. - atol : - This attribute can not be randomized and is inherited from the base Flight object. - name : - This attribute can not be randomized and is inherited from the base Flight object. - equations_of_motion: - This attribute can not be randomized and is inherited from the base Flight object. - ode_solver: - This attribute can not be randomized and is inherited from the base Flight object. - simulation_mode: - This attribute can not be randomized and is inherited from the base Flight object. """ def __init__( @@ -122,21 +106,6 @@ def __init__( else: self.time_overshoot = time_overshoot - # The following attributes are not randomized and are set to the values - # of the flight object. Move to helper to reduce statements in __init__. - self._copy_nonrandom_attrs(flight) - - def _copy_nonrandom_attrs(self, flight): - """Copy non-randomized attributes from the base Flight object.""" - self.max_time_step = flight.max_time_step - self.min_time_step = flight.min_time_step - self.rtol = flight.rtol - self.atol = flight.atol - self.name = flight.name - self.equations_of_motion = flight.equations_of_motion - self.ode_solver = flight.ode_solver - self.simulation_mode = flight.simulation_mode - def _validate_initial_solution(self, initial_solution): if initial_solution is not None: if isinstance(initial_solution, (tuple, list)): @@ -188,13 +157,13 @@ def create_object(self): initial_solution=self.initial_solution, terminate_on_apogee=self.terminate_on_apogee, max_time=self.max_time, - max_time_step=self.max_time_step, - min_time_step=self.min_time_step, - rtol=self.rtol, - atol=self.atol, + max_time_step=self.obj.max_time_step, + min_time_step=self.obj.min_time_step, + rtol=self.obj.rtol, + atol=self.obj.atol, time_overshoot=self.time_overshoot, - name=self.name, - equations_of_motion=self.equations_of_motion, - ode_solver=self.ode_solver, - simulation_mode=self.simulation_mode, + name=self.obj.name, + equations_of_motion=self.obj.equations_of_motion, + ode_solver=self.obj.ode_solver, + simulation_mode=self.obj.simulation_mode, ) diff --git a/tests/unit/stochastic/test_stochastic_flight.py b/tests/unit/stochastic/test_stochastic_flight.py index a5e0c5032..e03917475 100644 --- a/tests/unit/stochastic/test_stochastic_flight.py +++ b/tests/unit/stochastic/test_stochastic_flight.py @@ -7,23 +7,36 @@ def test_stochastic_flight_create_object(stochastic_flight): assert isinstance(obj, Flight) -def test_stochastic_flight_default_attributes(stochastic_flight, flight_calisto_robust): +def test_stochastic_flight_inherited_attributes(calisto_robust, example_spaceport_env): + flight = Flight( + rocket=calisto_robust, + environment=example_spaceport_env, + rail_length=5.2, + max_time_step=10, + min_time_step=0.01, + rtol=1e-4, + atol=1e-6, + name="FlightName", + equations_of_motion="solid_propulsion", + ode_solver="BDF", + simulation_mode="3 DOF", + ) + stochastic_flight = StochasticFlight(flight=flight) + obj = stochastic_flight.create_object() - assert flight_calisto_robust.max_time_step == obj.max_time_step - assert flight_calisto_robust.min_time_step == obj.min_time_step - assert flight_calisto_robust.rtol == obj.rtol - assert flight_calisto_robust.atol == obj.atol - assert flight_calisto_robust.name == obj.name - assert flight_calisto_robust.equations_of_motion == obj.equations_of_motion - assert flight_calisto_robust.ode_solver == obj.ode_solver - assert flight_calisto_robust.simulation_mode == obj.simulation_mode + assert flight.max_time_step == obj.max_time_step + assert flight.min_time_step == obj.min_time_step + assert flight.rtol == obj.rtol + assert flight.atol == obj.atol + assert flight.name == obj.name + assert flight.equations_of_motion == obj.equations_of_motion + assert flight.ode_solver == obj.ode_solver + assert flight.simulation_mode == obj.simulation_mode def test_stochastic_flight_optional_attributes(flight_calisto_robust): stochastic_flight = StochasticFlight( flight=flight_calisto_robust, - inclination=(84.7, 1), - heading=(53, 2), terminate_on_apogee=True, time_overshoot=True, max_time=987.6,