From be42228c8d55f38b38a11900f068e2ff5590780b Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 30 Mar 2026 09:56:42 -0400 Subject: [PATCH 1/8] ENH: added discrte and continuous controller functions --- rocketpy/rocket/rocket.py | 36 +++++++++++++++++++++++++++++++++++ rocketpy/simulation/flight.py | 3 +++ 2 files changed, 39 insertions(+) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index e3692d2e8..2fa204fd7 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1952,6 +1952,42 @@ def add_thrust_eccentricity(self, x, y): self.thrust_eccentricity_y = y return self + def add_discrete_controller(self, + controller_function, + refresh_rate, + interactive_objects=None, + initial_observed_variables=None, + name=None + ): + + controller = _Controller( + controller_function=controller_function, + sampling_rate=refresh_rate, + interactive_objects=interactive_objects, + initial_observed_variables=initial_observed_variables, + name=name) + + self._add_controllers(controller) + + return None + + def add_continuous_controller(self, + controller_function, + interactive_objects=None, + initial_observed_variables=None, + name=None + ): + + controller = _Controller( + controller_function=controller_function, + sampling_rate=np.inf, + interactive_objects=interactive_objects, + initial_observed_variables=initial_observed_variables, + name=name) + + self._add_controllers(controller) + return controller + def draw(self, vis_args=None, plane="xz", *, filename=None): """Draws the rocket in a matplotlib figure. diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 1443d1d80..1104769af 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -4497,6 +4497,9 @@ def add_parachutes(self, parachutes, t_init, t_end): def add_controllers(self, controllers, t_init, t_end): for controller in controllers: + # Skip node creation for continuous controllers + if math.isinf(controller.sampling_rate): + continue # Calculate start of sampling time nodes controller_time_step = 1 / controller.sampling_rate controller_node_list = [ From 6f11c5ed5296f72f15c1b81117f4e9a7f5f8d8df Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 30 Mar 2026 10:19:35 -0400 Subject: [PATCH 2/8] linted rocketpy/rocket/rocket.py --- rocketpy/rocket/rocket.py | 42 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 2fa204fd7..ec5772ec4 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1952,38 +1952,42 @@ def add_thrust_eccentricity(self, x, y): self.thrust_eccentricity_y = y return self - def add_discrete_controller(self, - controller_function, - refresh_rate, - interactive_objects=None, - initial_observed_variables=None, - name=None - ): + def add_discrete_controller( + self, + controller_function, + refresh_rate, + interactive_objects=None, + initial_observed_variables=None, + name=None, + ): controller = _Controller( - controller_function=controller_function, - sampling_rate=refresh_rate, - interactive_objects=interactive_objects, - initial_observed_variables=initial_observed_variables, - name=name) + controller_function=controller_function, + sampling_rate=refresh_rate, + interactive_objects=interactive_objects, + initial_observed_variables=initial_observed_variables, + name=name, + ) self._add_controllers(controller) return None - def add_continuous_controller(self, - controller_function, - interactive_objects=None, - initial_observed_variables=None, - name=None - ): + def add_continuous_controller( + self, + controller_function, + interactive_objects=None, + initial_observed_variables=None, + name=None, + ): controller = _Controller( controller_function=controller_function, sampling_rate=np.inf, interactive_objects=interactive_objects, initial_observed_variables=initial_observed_variables, - name=name) + name=name, + ) self._add_controllers(controller) return controller From 31d80b9e25a1e1d99935e5287e8dbbe48ee972c0 Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 30 Mar 2026 10:23:51 -0400 Subject: [PATCH 3/8] lint fix return None to return self in discrete controller function --- rocketpy/rocket/rocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index ec5772ec4..9800d9e46 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1971,7 +1971,7 @@ def add_discrete_controller( self._add_controllers(controller) - return None + return self def add_continuous_controller( self, From 3c28452f3f016555ce62bb17bddc25e15edbb7dc Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 11 May 2026 16:28:57 -0400 Subject: [PATCH 4/8] BUG: fixed cont ctrl calls integrated into the ODE solver --- rocketpy/control/controller.py | 6 +++-- rocketpy/rocket/rocket.py | 46 ++++++++++++++++------------------ rocketpy/simulation/flight.py | 11 +++++++- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/rocketpy/control/controller.py b/rocketpy/control/controller.py index e81e70915..45e32c815 100644 --- a/rocketpy/control/controller.py +++ b/rocketpy/control/controller.py @@ -1,3 +1,4 @@ +import math from inspect import signature from typing import Iterable @@ -17,7 +18,7 @@ def __init__( self, interactive_objects, controller_function, - sampling_rate, + sampling_rate=math.inf, initial_observed_variables=None, name="Controller", ): @@ -72,7 +73,8 @@ def __init__( relevant information in the `observed_variables` list. .. note:: The function will be called according to the sampling rate - specified. + specified. If unspecified, the default sampling rate is set to infinity, meaning that the + controller function will be called at every step of the simulation. sampling_rate : float The sampling rate of the controller function in Hertz (Hz). This means that the controller function will be called every diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 9800d9e46..1cf90489e 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1952,42 +1952,38 @@ def add_thrust_eccentricity(self, x, y): self.thrust_eccentricity_y = y return self - def add_discrete_controller( - self, - controller_function, - refresh_rate, - interactive_objects=None, - initial_observed_variables=None, - name=None, - ): + def add_discrete_controller(self, + controller_function, + refresh_rate, + interactive_objects=None, + initial_observed_variables=None, + name=None + ): controller = _Controller( - controller_function=controller_function, - sampling_rate=refresh_rate, - interactive_objects=interactive_objects, - initial_observed_variables=initial_observed_variables, - name=name, - ) + controller_function=controller_function, + sampling_rate=refresh_rate, + interactive_objects=interactive_objects, + initial_observed_variables=initial_observed_variables, + name=name) self._add_controllers(controller) - return self + return None - def add_continuous_controller( - self, - controller_function, - interactive_objects=None, - initial_observed_variables=None, - name=None, - ): + def add_continuous_controller(self, + controller_function, + interactive_objects=None, + initial_observed_variables=None, + name=None + ): controller = _Controller( controller_function=controller_function, - sampling_rate=np.inf, + sampling_rate=math.inf, interactive_objects=interactive_objects, initial_observed_variables=initial_observed_variables, - name=name, - ) + name=name) self._add_controllers(controller) return controller diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 1104769af..4d29141fb 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -786,7 +786,14 @@ def __simulate(self, verbose): self.y_sol = phase.solver.y if verbose: print(f"Current Simulation Time: {self.t:3.4f} s", end="\r") - + for controller in self._continuous_controllers: + controller( + self.t, + self.y_sol, + self.solution[:-1], + self.sensors, + self.env, + ) if self.__check_simulation_events(phase, phase_index, node_index): break # Stop if simulation termination event occurred @@ -1585,6 +1592,8 @@ def __init_equations_of_motion(self): def __init_controllers(self): """Initialize controllers and sensors""" self._controllers = self.rocket._controllers[:] + self._continuous_controllers = [c for c in self._controllers if math.isinf(c.sampling_rate)] + self._discrete_controllers = [c for c in self._controllers if not math.isinf(c.sampling_rate)] self.sensors = self.rocket.sensors.get_components() # reset controllable object to initial state (only airbrakes for now) From a4de476c0e13b57ee866e1f3ed07f3d646761b60 Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 11 May 2026 16:40:57 -0400 Subject: [PATCH 5/8] lint fixes --- rocketpy/rocket/rocket.py | 42 +++++++++++++++++++---------------- rocketpy/simulation/flight.py | 8 +++++-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 1cf90489e..7612a52d7 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1952,38 +1952,42 @@ def add_thrust_eccentricity(self, x, y): self.thrust_eccentricity_y = y return self - def add_discrete_controller(self, - controller_function, - refresh_rate, - interactive_objects=None, - initial_observed_variables=None, - name=None - ): + def add_discrete_controller( + self, + controller_function, + refresh_rate, + interactive_objects=None, + initial_observed_variables=None, + name=None, + ): controller = _Controller( - controller_function=controller_function, - sampling_rate=refresh_rate, - interactive_objects=interactive_objects, - initial_observed_variables=initial_observed_variables, - name=name) + controller_function=controller_function, + sampling_rate=refresh_rate, + interactive_objects=interactive_objects, + initial_observed_variables=initial_observed_variables, + name=name, + ) self._add_controllers(controller) return None - def add_continuous_controller(self, - controller_function, - interactive_objects=None, - initial_observed_variables=None, - name=None - ): + def add_continuous_controller( + self, + controller_function, + interactive_objects=None, + initial_observed_variables=None, + name=None, + ): controller = _Controller( controller_function=controller_function, sampling_rate=math.inf, interactive_objects=interactive_objects, initial_observed_variables=initial_observed_variables, - name=name) + name=name, + ) self._add_controllers(controller) return controller diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index 4d29141fb..82b63c1ed 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1592,8 +1592,12 @@ def __init_equations_of_motion(self): def __init_controllers(self): """Initialize controllers and sensors""" self._controllers = self.rocket._controllers[:] - self._continuous_controllers = [c for c in self._controllers if math.isinf(c.sampling_rate)] - self._discrete_controllers = [c for c in self._controllers if not math.isinf(c.sampling_rate)] + self._continuous_controllers = [ + c for c in self._controllers if math.isinf(c.sampling_rate) + ] + self._discrete_controllers = [ + c for c in self._controllers if not math.isinf(c.sampling_rate) + ] self.sensors = self.rocket.sensors.get_components() # reset controllable object to initial state (only airbrakes for now) From 77bfd81ef41764f05bbc8f99ed661859e00a5599 Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 11 May 2026 17:29:20 -0400 Subject: [PATCH 6/8] lint fix: removed return None --- rocketpy/rocket/rocket.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index e2641a717..51dd03876 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -2021,8 +2021,6 @@ def add_discrete_controller( self._add_controllers(controller) - return None - def add_continuous_controller( self, controller_function, From df331fa888061772ec9a52451ce2f77431ee9228 Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 11 May 2026 18:42:13 -0400 Subject: [PATCH 7/8] implemented copilot suggestions --- rocketpy/rocket/rocket.py | 10 ++++++++-- rocketpy/simulation/flight.py | 3 --- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 51dd03876..bb22bc231 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -2008,8 +2008,11 @@ def add_discrete_controller( refresh_rate, interactive_objects=None, initial_observed_variables=None, - name=None, + name="Controller", ): + """Creates a new discrete controller, storing its parameters such as + controller function, refresh rate, and interactive objects. The controller + will be called at the specified refresh rate during the simulation.""" controller = _Controller( controller_function=controller_function, @@ -2026,8 +2029,11 @@ def add_continuous_controller( controller_function, interactive_objects=None, initial_observed_variables=None, - name=None, + name="Controller", ): + """Creates a new continuous controller, storing its parameters such as + controller function and interactive objects. The controller will + be called at every time step of the simulation.""" controller = _Controller( controller_function=controller_function, diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index ea1338c42..45e5189a4 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -1586,9 +1586,6 @@ def __init_controllers(self): self._continuous_controllers = [ c for c in self._controllers if math.isinf(c.sampling_rate) ] - self._discrete_controllers = [ - c for c in self._controllers if not math.isinf(c.sampling_rate) - ] self.sensors = self.rocket.sensors.get_components() # reset controllable object to initial state (only airbrakes for now) From ccce75dcf67cff7eb0edd8e31cf93f0f65c41892 Mon Sep 17 00:00:00 2001 From: Malmahrouqi3 Date: Mon, 11 May 2026 18:45:21 -0400 Subject: [PATCH 8/8] lint fix, extra spaces --- rocketpy/rocket/rocket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index bb22bc231..ce7df410a 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -2011,7 +2011,7 @@ def add_discrete_controller( name="Controller", ): """Creates a new discrete controller, storing its parameters such as - controller function, refresh rate, and interactive objects. The controller + controller function, refresh rate, and interactive objects. The controller will be called at the specified refresh rate during the simulation.""" controller = _Controller( @@ -2032,7 +2032,7 @@ def add_continuous_controller( name="Controller", ): """Creates a new continuous controller, storing its parameters such as - controller function and interactive objects. The controller will + controller function and interactive objects. The controller will be called at every time step of the simulation.""" controller = _Controller(