Skip to content

Commit 6f640a1

Browse files
Release: actuator module (#10)
* Add actuator dynamics in TVC and ThrottleControl * Refactor actuator_tau parameters to use None as default in TVC and ThrottleControl * Add actuator dynamics to RollControl * Move actuators to a new actuator folder and rename actuator classes * Refactor roll, throttle, and thrust vector acutator with a new actuator class * Add pytest for all actuators * Update rocket.py and flight.py for actuator class update * Fix actuator class calls * Update active control example with new actuator class * Update time_overshoot in flight.py to align rocketpy commit #45a89 RocketPy-Team@45a891e * Fix actuator unit tests * Enhance ThrustVectorActuator2D with serialization methods and improve documentation * Fix comment msg * Fix pylint unused parameters * Remove unused import * Fix pylint warnings * Refactor __run_in_serial() to fix pylint too much statement warning * Fix: use warning instead of printf * Fix comment * Fix: use warning instead of printf * Run ruff format * Fix thrust3 and effective_thrust name * Fix pytests * Fix pytest utilities * Fix integration/gnss pytest * Update readme --------- Co-authored-by: RickyRicato <rickywang44@gmail.com> Co-authored-by: ChiChun Wang <99960850+chichunwang@users.noreply.github.com>
1 parent df3ea41 commit 6f640a1

27 files changed

Lines changed: 1611 additions & 825 deletions

README.md

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,18 @@
33
ActiveRocketPy is the enhanced version of [RocketPy](https://github.com/RocketPy-Team/RocketPy), a powerful Python package for simulating the trajectories of high-power rockets. This fork introduces a range of active control and guidance, navigation, and control (GNC) features.
44

55
## Main features (ActiveRocketPy)
6-
1. **Thrust Vector Control (TVC)**
7-
- Implementation of TVC control class
8-
- Feed X & Y gimbal angles through a control function
9-
- Actuator dynamics and limits for realistic TVC simulations (WIP)
10-
11-
2. **Roll Control**
12-
- Implementation of roll control class
13-
- Feed ideal roll torque through a control function
14-
- Actuator dynamics and limits for realistic roll control simulations (WIP)
15-
16-
3. **Throttle Control**
17-
- Implementation of throttle control class
18-
- Feed throttle percentage through a control function
19-
- Actuator dynamics and limits for realistic throttle control simulations (WIP)
20-
21-
4. **Step simulation**
6+
7+
1. **Actuator Classes for active control**
8+
- Actuator dynamics and limits for realistic active flight simulations
9+
- **Thrust Vector Control (TVC)**
10+
- Feed X & Y gimbal angles through a control function
11+
- **Roll Control**
12+
- Feed ideal roll torque through a control function
13+
- **Throttle Control**
14+
- Feed throttle percentage through a control function
15+
- Known limitation: The mass properties are pre-calculated before flight according to max flow rate and burn time. Throttle commands does not affect the change of the mass properties in-flight. It is equivalent to throttling the Isp of rocket engine while the flow rate remains constant. The engine is cut off when burn time is reached
16+
17+
2. **Step simulation**
2218
- Step through the simulation one time step at a time
2319
- Update control inputs and step simulations in loop
2420

@@ -76,7 +72,6 @@ Check out documentation details using the links below:
7672
- [RocketPy Technical Documentation](https://docs.rocketpy.org/en/latest/technical/index.html)
7773
- [RocketPy Flight Examples](https://docs.rocketpy.org/en/latest/examples/index.html)
7874

79-
8075
<br>
8176

8277
# Getting Started
@@ -100,7 +95,6 @@ pip install -r requirements-tests.txt # install test/dev requirements
10095
10196
ActiveRocketPy is forked and maintained by [ZuoRen Chen](https://github.com/zuorenchen), along with the team from [Advanced Rocket Research Center (ARRC)](https://github.com/ARRC-Rocket).
10297

103-
10498
## Citation
10599

106100
If you run ActiveRocketPy in your research, please consider citing:
@@ -114,4 +108,5 @@ If you run ActiveRocketPy in your research, please consider citing:
114108
url = {https://github.com/ARRC-Rocket/ActiveRocketPy}
115109
}
116110
```
117-
To cite RocketPy, please check its repository for the latest citation information: [RocketPy](https://github.com/RocketPy-Team/RocketPy)
111+
112+
To cite RocketPy, please check its repository for the latest citation information: [RocketPy](https://github.com/RocketPy-Team/RocketPy)

docs/examples/halcyon_flight_sim_roll_throttle.ipynb renamed to docs/examples/halcyon_flight_sim_active_control.ipynb

Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,11 @@
4444
" Accelerometer,\n",
4545
" Environment,\n",
4646
" Flight,\n",
47-
" Function,\n",
48-
" GnssReceiver,\n",
4947
" Gyroscope,\n",
5048
" Rocket,\n",
5149
")\n",
5250
"from rocketpy.motors import CylindricalTank, Fluid, HybridMotor\n",
5351
"from rocketpy.motors.tank import MassFlowRateBasedTank\n",
54-
"from rocketpy.tools import quaternions_to_spin\n",
5552
"\n",
5653
"plt.style.use(\"seaborn-v0_8-colorblind\")"
5754
]
@@ -284,30 +281,36 @@
284281
"outputs": [],
285282
"source": [
286283
"def tvc_controller_function(\n",
287-
" time, sampling_rate, state, state_history, observed_variables, tvc, sensors\n",
284+
" time,\n",
285+
" sampling_rate,\n",
286+
" state,\n",
287+
" state_history,\n",
288+
" observed_variables,\n",
289+
" thrust_vector_control,\n",
290+
" sensors,\n",
288291
"):\n",
289292
" # state = [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz]\n",
290293
"\n",
291294
" # print(time)\n",
292295
"\n",
293-
" tvc.gimbal_angle_x = 0\n",
294-
" tvc.gimbal_angle_y = 0\n",
296+
" thrust_vector_control.gimbal_angle_x = 0\n",
297+
" thrust_vector_control.gimbal_angle_y = 0\n",
295298
" # Return variables of interest to be saved in the observed_variables list\n",
296299
" return (\n",
297300
" time,\n",
298-
" tvc.gimbal_angle_x,\n",
299-
" tvc.gimbal_angle_y,\n",
301+
" thrust_vector_control.gimbal_angle_x,\n",
302+
" thrust_vector_control.gimbal_angle_y,\n",
300303
" )\n",
301304
"\n",
302305
"\n",
303-
"tvc, tvc_controller = HALCYON.add_tvc(\n",
304-
" gimbal_range=10,\n",
306+
"thrust_vector_control, tvc_controller = HALCYON.add_thrust_vector_control(\n",
307+
" controller_function=tvc_controller_function,\n",
305308
" sampling_rate=100,\n",
309+
" max_gimbal_angle=10,\n",
306310
" gimbal_rate_limit=100,\n",
307-
" controller_function=tvc_controller_function,\n",
308311
" return_controller=True,\n",
309312
")\n",
310-
"tvc.info()\n",
313+
"thrust_vector_control.x.info()\n",
311314
"tvc_controller.info()"
312315
]
313316
},
@@ -373,17 +376,14 @@
373376
" )\n",
374377
"\n",
375378
" # Return variables of interest to be saved in the observed_variables list\n",
376-
" return (\n",
377-
" time,\n",
378-
" roll_control.roll_torque,\n",
379-
" )\n",
379+
" return (time, roll_control.roll_torque, gyro_data, accel_data)\n",
380380
"\n",
381381
"\n",
382382
"roll_control, roll_controller = HALCYON.add_roll_control(\n",
383-
" max_roll_torque=10,\n",
384-
" sampling_rate=100,\n",
385-
" torque_rate_limit=100,\n",
386383
" controller_function=roll_controller_function,\n",
384+
" sampling_rate=100,\n",
385+
" max_roll_torque=10,\n",
386+
" torque_rate_limit=1000,\n",
387387
" return_controller=True,\n",
388388
")\n",
389389
"roll_control.info()\n",
@@ -408,33 +408,26 @@
408408
" # state = [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz]\n",
409409
"\n",
410410
" # Throttle command: 0.5 Hz sinusoid, centered at 0.8, amplitude 0.2\n",
411-
" throttle_control.throttle = 0.8 + 0.2 * np.sin(2 * np.pi * 0.5 * time)\n",
411+
" throttle_command = 0.8 + 0.2 * np.sin(2 * np.pi * 0.5 * time)\n",
412+
" throttle_control.throttle = throttle_command\n",
413+
" throttle_actual = throttle_control.throttle\n",
412414
"\n",
413415
" # Return variables of interest to be saved in the observed_variables list\n",
414416
" return (\n",
415417
" time,\n",
416-
" throttle_control.throttle,\n",
417-
" )"
418-
]
419-
},
420-
{
421-
"cell_type": "code",
422-
"execution_count": null,
423-
"metadata": {},
424-
"outputs": [],
425-
"source": [
418+
" throttle_command,\n",
419+
" throttle_actual,\n",
420+
" )\n",
421+
"\n",
422+
"\n",
426423
"throttle_obj, throttle_controller = HALCYON.add_throttle_control(\n",
427424
" controller_function=throttle_controller_function,\n",
428425
" sampling_rate=100,\n",
429426
" throttle_range=(0.0, 1.0),\n",
430427
" throttle_rate_limit=100,\n",
431-
" initial_throttle=1.0,\n",
428+
" throttle_time_constant=0.5,\n",
432429
" return_controller=True,\n",
433-
" clamp=True,\n",
434-
")\n",
435-
"print(\"has add_throttle_control:\", hasattr(HALCYON, \"add_throttle_control\"))\n",
436-
"print(\"has throttle_control:\", hasattr(HALCYON, \"throttle_control\"))\n",
437-
"print(\"throttle_control:\", getattr(HALCYON, \"throttle_control\", None))"
430+
")"
438431
]
439432
},
440433
{
@@ -461,22 +454,24 @@
461454
" verbose=False,\n",
462455
")\n",
463456
"# test_flight.plots.attitude_data()\n",
464-
"# ===== 先看 throttle 有沒有真的變 =====\n",
457+
"# ===== Plot commanded throttle vs filtered actuator output =====\n",
465458
"obs = np.array(throttle_controller.observed_variables)\n",
466459
"\n",
467460
"t_cmd = obs[:, 0]\n",
468461
"th_cmd = obs[:, 1]\n",
462+
"th_act = obs[:, 2]\n",
469463
"\n",
470464
"plt.figure()\n",
471-
"plt.plot(t_cmd, th_cmd, label=\"Throttle\")\n",
465+
"plt.plot(t_cmd, th_cmd, label=\"Throttle command\")\n",
466+
"plt.plot(t_cmd, th_act, label=\"Filtered throttle\")\n",
472467
"plt.xlabel(\"Time (s)\")\n",
473468
"plt.ylabel(\"Throttle\")\n",
474-
"plt.title(\"Throttle Command\")\n",
469+
"plt.title(\"Throttle Command vs Actuator Output\")\n",
475470
"plt.legend()\n",
476471
"plt.grid()\n",
477472
"plt.show()\n",
478473
"\n",
479-
"# ===== 再看推力有沒有跟著變 =====\n",
474+
"# ===== Then check thrust response =====\n",
480475
"plt.figure()\n",
481476
"plt.plot(\n",
482477
" test_flight.net_thrust[:, 0], test_flight.net_thrust[:, 1], label=\"Effective Thrust\"\n",
@@ -491,15 +486,6 @@
491486
"time1, ax, ay, az = zip(*accelerometer_clean.measured_data)\n",
492487
"time2, gx, gy, gz = zip(*gyro_clean.measured_data)\n",
493488
"\n",
494-
"# plt.plot(time1, ax, label=\"Accelerometer X\")\n",
495-
"# plt.plot(time1, ay, label=\"Accelerometer Y\")\n",
496-
"# plt.plot(time1, az, label=\"Accelerometer Z\")\n",
497-
"# plt.xlabel(\"Time (s)\")\n",
498-
"# plt.ylabel(\"Acceleration (m/s^2)\")\n",
499-
"# plt.legend()\n",
500-
"# plt.grid()\n",
501-
"# plt.show()\n",
502-
"\n",
503489
"plt.plot(time2, gx, label=\"Gyroscope X\")\n",
504490
"plt.plot(time2, gy, label=\"Gyroscope Y\")\n",
505491
"plt.plot(time2, gz, label=\"Gyroscope Z\")\n",
@@ -532,7 +518,7 @@
532518
],
533519
"metadata": {
534520
"kernelspec": {
535-
"display_name": "Python 3",
521+
"display_name": ".venv (3.14.3.final.0)",
536522
"language": "python",
537523
"name": "python3"
538524
},
@@ -546,7 +532,7 @@
546532
"name": "python",
547533
"nbconvert_exporter": "python",
548534
"pygments_lexer": "ipython3",
549-
"version": "3.12.10"
535+
"version": "3.14.3"
550536
}
551537
},
552538
"nbformat": 4,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
class _RollActuatorPrints:
2+
"""Class that contains all roll actuator prints."""
3+
4+
def __init__(self, roll_actuator):
5+
"""Initializes _RollActuatorPrints class
6+
7+
Parameters
8+
----------
9+
roll_actuator: rocketpy.RollActuator
10+
Instance of the RollActuator class.
11+
12+
Returns
13+
-------
14+
None
15+
"""
16+
self.roll_actuator = roll_actuator
17+
18+
def basics(self):
19+
"""Prints information of the roll actuator."""
20+
print(f"Information of {self.roll_actuator.name}:")
21+
print("----------------------------------")
22+
print(f"Torque demand rate: {self.roll_actuator.demand_rate} Hz")
23+
print(
24+
f"Torque range: {self.roll_actuator.actuator_range[0]:.2f} to {self.roll_actuator.actuator_range[1]:.2f} N·m"
25+
)
26+
print(f"Torque rate limit: {self.roll_actuator.actuator_rate_limit} N·m/s")
27+
print(
28+
f"Torque clamping: {'Enabled' if self.roll_actuator.clamp else 'Disabled'}"
29+
)
30+
print(f"Torque time constant: {self.roll_actuator.actuator_time_constant} sec")
31+
print(f"Current roll torque: {self.roll_actuator.roll_torque:.2f} N·m")
32+
33+
def all(self):
34+
"""Prints all information of the roll actuator."""
35+
self.basics()

rocketpy/prints/roll_control_prints.py

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
class _ThrottleActuatorPrints:
2+
def __init__(self, throttle_actuator):
3+
"""Initializes _ThrottleActuatorPrints class
4+
5+
Parameters
6+
----------
7+
throttle_actuator: rocketpy.ThrottleActuator
8+
Instance of the ThrottleActuator class.
9+
10+
Returns
11+
-------
12+
None
13+
"""
14+
self.throttle_actuator = throttle_actuator
15+
16+
def basics(self):
17+
"""Prints information of the throttle actuator."""
18+
print(f"Information of {self.throttle_actuator.name}:")
19+
print("----------------------------------")
20+
print(f"Throttle demand rate: {self.throttle_actuator.demand_rate} Hz")
21+
print(
22+
f"Throttle range: {self.throttle_actuator.actuator_range[0]:.2f} to {self.throttle_actuator.actuator_range[1]:.2f}"
23+
)
24+
print(f"Throttle rate limit: {self.throttle_actuator.actuator_rate_limit}")
25+
print(
26+
f"Throttle Clamping: {'Enabled' if self.throttle_actuator.clamp else 'Disabled'}"
27+
)
28+
print(
29+
f"Throttle time constant: {self.throttle_actuator.actuator_time_constant} sec"
30+
)
31+
print(f"Current throttle: {self.throttle_actuator.throttle:.2f}")
32+
33+
def all(self):
34+
"""Prints all information of the throttle actuator."""
35+
self.basics()

rocketpy/prints/throttle_control_prints.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)