Skip to content

Commit 46b54c5

Browse files
committed
Enhance ThrustVectorActuator2D with serialization methods and improve documentation
1 parent 7c7587d commit 46b54c5

2 files changed

Lines changed: 133 additions & 7 deletions

File tree

rocketpy/rocket/actuator/thrust_vector.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def __init__(
184184
Parameters
185185
----------
186186
name : str, optional
187-
Name of the dual-axis thrust vector actuator. Default is "Thrust Vector Control".
187+
Name of the dual-axis thrust vector actuator. Default is "Thrust Vector Control (X/Y)-axis".
188188
demand_rate : int, optional
189189
Demand rate of the dual-axis thrust vector actuator in Hz. Default is 100 Hz.
190190
None indicates a continuous-time actuator.
@@ -201,19 +201,20 @@ def __init__(
201201
exceeds the maximum value. Default is True.
202202
initial_gimbal_angle : float, optional
203203
Initial gimbal angle in deg. Default is 0.0 (no gimbal).
204-
roll_torque_time_constant : float, optional
205-
Time constant for the roll torque actuator dynamics (first-order IIR
204+
gimbal_time_constant : float, optional
205+
Time constant for the gimbal actuator dynamics (first-order IIR
206206
filter) in seconds. If None, no actuator dynamics are applied.
207-
Must be non-negative. Default is None. demand_rate must be specified if roll_torque_time_constant is not None.
207+
Must be non-negative. Default is None. demand_rate must be specified if gimbal_time_constant is not None.
208208
209209
210210
Returns
211211
-------
212212
None
213213
"""
214+
self.name = name
214215

215216
self.x = ThrustVectorActuator(
216-
name=name + " X-axis",
217+
name=self.name + " X-axis",
217218
demand_rate=demand_rate,
218219
max_gimbal_angle=max_gimbal_angle,
219220
gimbal_rate_limit=gimbal_rate_limit,
@@ -222,7 +223,7 @@ def __init__(
222223
gimbal_time_constant=gimbal_time_constant,
223224
)
224225
self.y = ThrustVectorActuator(
225-
name=name + " Y-axis",
226+
name=self.name + " Y-axis",
226227
demand_rate=demand_rate,
227228
max_gimbal_angle=max_gimbal_angle,
228229
gimbal_rate_limit=gimbal_rate_limit,
@@ -233,7 +234,7 @@ def __init__(
233234

234235
@property
235236
def gimbal_angle_x(self):
236-
"""Returns the current gimbal angle around the y-axis (yaw)."""
237+
"""Returns the current gimbal angle around the x-axis (pitch)."""
237238
return self.x.gimbal_angle
238239

239240
@gimbal_angle_x.setter
@@ -267,3 +268,45 @@ def gimbal_angles(self, value):
267268
"""
268269
self.gimbal_angle_x = value[0]
269270
self.gimbal_angle_y = value[1]
271+
272+
def info(self):
273+
"""Prints summarized information of the thrust vector actuator.
274+
275+
Returns
276+
-------
277+
None
278+
"""
279+
self.x.info()
280+
self.y.info()
281+
282+
def all_info(self):
283+
"""Prints all information of the thrust vector actuator.
284+
285+
Returns
286+
-------
287+
None
288+
"""
289+
self.info()
290+
291+
def to_dict(self, **kwargs): # pylint: disable=unused-argument
292+
return {
293+
"name": self.name,
294+
"demand_rate": self.x.demand_rate,
295+
"max_gimbal_angle": self.x.actuator_range[1],
296+
"gimbal_rate_limit": self.x.actuator_rate_limit,
297+
"clamp": self.x.clamp,
298+
"initial_gimbal_angle": self.x.actuator_initial_output,
299+
"gimbal_time_constant": self.x.actuator_time_constant,
300+
}
301+
302+
@classmethod
303+
def from_dict(cls, data):
304+
return cls(
305+
name=data.get("name"),
306+
demand_rate=data.get("demand_rate"),
307+
max_gimbal_angle=data.get("max_gimbal_angle"),
308+
gimbal_rate_limit=data.get("gimbal_rate_limit"),
309+
clamp=data.get("clamp"),
310+
initial_gimbal_angle=data.get("initial_gimbal_angle"),
311+
gimbal_time_constant=data.get("gimbal_time_constant"),
312+
)

tests/unit/rocket/test_actuators.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,89 @@ def test_clamping_individual_axes(self):
326326
actuator.y.actuator_output = -15.0
327327
assert actuator.gimbal_angle_y == -10.0
328328

329+
def test_to_dict(self):
330+
"""Test to_dict serialization for ThrustVectorActuator2D."""
331+
actuator = ThrustVectorActuator2D(
332+
name="Custom 2D TVC",
333+
demand_rate=50,
334+
max_gimbal_angle=8.0,
335+
gimbal_rate_limit=3.0,
336+
clamp=True,
337+
initial_gimbal_angle=2.0,
338+
gimbal_time_constant=0.1,
339+
)
340+
data = actuator.to_dict()
341+
assert data["name"] == "Custom 2D TVC"
342+
assert data["demand_rate"] == 50
343+
assert data["max_gimbal_angle"] == 8.0
344+
assert data["gimbal_rate_limit"] == 3.0
345+
assert data["clamp"] is True
346+
assert data["initial_gimbal_angle"] == 2.0
347+
assert data["gimbal_time_constant"] == 0.1
348+
349+
def test_from_dict(self):
350+
"""Test from_dict deserialization for ThrustVectorActuator2D."""
351+
data = {
352+
"name": "Test 2D TVC",
353+
"demand_rate": 75,
354+
"max_gimbal_angle": 12.0,
355+
"gimbal_rate_limit": 4.0,
356+
"clamp": False,
357+
"initial_gimbal_angle": 3.0,
358+
"gimbal_time_constant": 0.2,
359+
}
360+
actuator = ThrustVectorActuator2D.from_dict(data)
361+
assert actuator.x.name == "Test 2D TVC X-axis"
362+
assert actuator.y.name == "Test 2D TVC Y-axis"
363+
assert actuator.x.demand_rate == 75
364+
assert actuator.y.demand_rate == 75
365+
assert actuator.gimbal_angles == (3.0, 3.0)
366+
assert actuator.x.actuator_range == (-12.0, 12.0)
367+
368+
def test_from_dict_roundtrip(self):
369+
"""Test roundtrip serialization/deserialization."""
370+
original = ThrustVectorActuator2D(
371+
name="Roundtrip Test",
372+
demand_rate=60,
373+
max_gimbal_angle=9.0,
374+
gimbal_rate_limit=2.5,
375+
clamp=True,
376+
initial_gimbal_angle=1.5,
377+
gimbal_time_constant=0.15,
378+
)
379+
data = original.to_dict()
380+
reconstructed = ThrustVectorActuator2D.from_dict(data)
381+
assert reconstructed.x.name == original.x.name
382+
assert reconstructed.x.demand_rate == original.x.demand_rate
383+
assert reconstructed.gimbal_angles == original.gimbal_angles
384+
385+
def test_info_methods(self):
386+
"""Test info and all_info methods for ThrustVectorActuator2D."""
387+
actuator = ThrustVectorActuator2D(
388+
name="Test 2D TVC",
389+
max_gimbal_angle=10.0,
390+
initial_gimbal_angle=2.0,
391+
)
392+
# These should not raise exceptions
393+
actuator.info()
394+
actuator.all_info()
395+
396+
def test_info_methods_with_custom_parameters(self):
397+
"""Test info methods with custom gimbal angles."""
398+
actuator = ThrustVectorActuator2D(
399+
name="Custom Info Test",
400+
demand_rate=200,
401+
max_gimbal_angle=15.0,
402+
gimbal_rate_limit=10.0,
403+
initial_gimbal_angle=5.0,
404+
gimbal_time_constant=0.05,
405+
)
406+
actuator.gimbal_angle_x = 7.5
407+
actuator.gimbal_angle_y = -3.2
408+
# These should not raise exceptions and reflect current state
409+
actuator.info()
410+
actuator.all_info()
411+
329412

330413
class TestActuatorDynamics:
331414
"""Test suite for actuator dynamics (rate limiting, time constants)."""

0 commit comments

Comments
 (0)