Skip to content

Commit 601acb0

Browse files
ENH: finalize continuous controller handling
Optimize continuous controller state-history handling to avoid repeated list rebuilding, align controller sampling-rate docs with None-based continuous mode, and add regression coverage to ensure continuous controllers do not create time nodes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ee372a2 commit 601acb0

3 files changed

Lines changed: 44 additions & 14 deletions

File tree

rocketpy/control/controller.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ def __init__(
7171
objects as needed. The function return statement can be used to save
7272
relevant information in the `observed_variables` list.
7373
74-
.. note:: The function will be called according to the sampling rate
75-
specified. If unspecified, the default sampling rate is set to None, meaning that the
76-
controller function will be called at every solver step of the simulation.
77-
sampling_rate : float
74+
.. note:: The function will be called according to the sampling
75+
rate specified. If `sampling_rate` is None, the controller
76+
function is called at every solver step of the simulation.
77+
sampling_rate : float, optional
7878
The sampling rate of the controller function in Hertz (Hz). This
7979
means that the controller function will be called every
80-
`1/sampling_rate` seconds.
80+
`1/sampling_rate` seconds. If None, it is treated as a
81+
continuous controller and called at every solver step.
8182
initial_observed_variables : list, optional
8283
A list of the initial values of the variables that the controller
8384
function returns. This list is used to initialize the

rocketpy/simulation/flight.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -777,14 +777,16 @@ def __simulate(self, verbose):
777777
self.y_sol = phase.solver.y
778778
if verbose:
779779
print(f"Current Simulation Time: {self.t:3.4f} s", end="\r")
780-
for controller in self._continuous_controllers:
781-
controller(
782-
self.t,
783-
self.y_sol,
784-
[step[1:] for step in self.solution[:-1]],
785-
self.sensors,
786-
self.env,
787-
)
780+
if self._continuous_controllers:
781+
for controller in self._continuous_controllers:
782+
controller(
783+
self.t,
784+
self.y_sol,
785+
self._controller_state_history,
786+
self.sensors,
787+
self.env,
788+
)
789+
self._controller_state_history.append(list(self.y_sol))
788790
if self.__check_simulation_events(phase, phase_index, node_index):
789791
break # Stop if simulation termination event occurred
790792

@@ -1544,6 +1546,7 @@ def __init_solver_monitors(self):
15441546

15451547
self.t_initial = self.initial_solution[0]
15461548
self.solution.append(self.initial_solution)
1549+
self._controller_state_history = [self.initial_solution[1:]]
15471550
self.t = self.solution[-1][0]
15481551
self.y_sol = self.solution[-1][1:]
15491552

tests/unit/simulation/test_flight_time_nodes.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
TimeNode.
33
"""
44

5-
# from rocketpy.rocket import Parachute, _Controller
5+
from rocketpy.control.controller import _Controller
66

77

88
def test_time_nodes_init(flight_calisto):
@@ -49,6 +49,32 @@ def test_time_nodes_add_node(flight_calisto):
4949
# TODO: implement this test
5050

5151

52+
def test_time_nodes_add_controllers_skips_continuous_controllers(flight_calisto):
53+
"""Ensure only discrete controllers create time nodes."""
54+
# Arrange
55+
discrete_controller = _Controller(
56+
interactive_objects=[],
57+
controller_function=lambda t, sr, sv, sh, ov, io: None,
58+
sampling_rate=10,
59+
name="Discrete",
60+
)
61+
continuous_controller = _Controller(
62+
interactive_objects=[],
63+
controller_function=lambda t, sr, sv, sh, ov, io: None,
64+
sampling_rate=None,
65+
name="Continuous",
66+
)
67+
time_nodes = flight_calisto.TimeNodes()
68+
69+
# Act
70+
time_nodes.add_controllers([discrete_controller, continuous_controller], 0, 1)
71+
72+
# Assert
73+
assert len(time_nodes) == 11
74+
assert all(node._controllers == [discrete_controller] for node in time_nodes)
75+
assert all(continuous_controller not in node._controllers for node in time_nodes)
76+
77+
5278
def test_time_nodes_sort(flight_calisto):
5379
time_nodes = flight_calisto.TimeNodes()
5480
time_nodes.add_node(3.0, [], [], [])

0 commit comments

Comments
 (0)