Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
543 changes: 543 additions & 0 deletions docs/examples/halcyon_flight_sim_active_control.ipynb

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions rocketpy/prints/roll_actuator_prints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class _RollActuatorPrints:
"""Class that contains all roll actuator prints."""

def __init__(self, roll_actuator):
"""Initializes _RollActuatorPrints class

Parameters
----------
roll_actuator: rocketpy.RollActuator
Instance of the RollActuator class.

Returns
-------
None
"""
self.roll_actuator = roll_actuator

def basics(self):
"""Prints information of the roll actuator."""
print(f"Information of {self.roll_actuator.name}:")
print("----------------------------------")
print(f"Torque demand rate: {self.roll_actuator.demand_rate} Hz")
print(
f"Torque range: {self.roll_actuator.actuator_range[0]:.2f} to {self.roll_actuator.actuator_range[1]:.2f} N·m"
)
print(f"Torque rate limit: {self.roll_actuator.actuator_rate_limit} N·m/s")
print(
f"Torque clamping: {'Enabled' if self.roll_actuator.clamp else 'Disabled'}"
)
print(f"Torque time constant: {self.roll_actuator.actuator_time_constant} sec")
print(f"Current roll torque: {self.roll_actuator.roll_torque:.2f} N·m")

def all(self):
"""Prints all information of the roll actuator."""
self.basics()
35 changes: 35 additions & 0 deletions rocketpy/prints/throttle_actuator_prints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class _ThrottleActuatorPrints:
def __init__(self, throttle_actuator):
"""Initializes _ThrottleActuatorPrints class

Parameters
----------
throttle_actuator: rocketpy.ThrottleActuator
Instance of the ThrottleActuator class.

Returns
-------
None
"""
self.throttle_actuator = throttle_actuator

def basics(self):
"""Prints information of the throttle actuator."""
print(f"Information of {self.throttle_actuator.name}:")
print("----------------------------------")
print(f"Throttle demand rate: {self.throttle_actuator.demand_rate} Hz")
print(
f"Throttle range: {self.throttle_actuator.actuator_range[0]:.2f} to {self.throttle_actuator.actuator_range[1]:.2f}"
)
print(f"Throttle rate limit: {self.throttle_actuator.actuator_rate_limit}")
print(
f"Throttle Clamping: {'Enabled' if self.throttle_actuator.clamp else 'Disabled'}"
)
print(
f"Throttle time constant: {self.throttle_actuator.actuator_time_constant} sec"
)
print(f"Current throttle: {self.throttle_actuator.throttle:.2f}")

def all(self):
"""Prints all information of the throttle actuator."""
self.basics()
41 changes: 41 additions & 0 deletions rocketpy/prints/thrust_vector_actuator_prints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class _ThrustVectorActuatorPrints:
"""Class that contains all thrust vector actuator prints."""

def __init__(self, thrust_vector_actuator):
"""Initializes _ThrustVectorActuatorPrints class

Parameters
----------
thrust_vector_actuator: rocketpy.ThrustVectorActuator
Instance of the thrust vector actuator class.

Returns
-------
None
"""
self.thrust_vector_actuator = thrust_vector_actuator

def basics(self):
"""Prints information of the thrust vector actuator."""
print(f"Information of {self.thrust_vector_actuator.name}:")
print("----------------------------------")
print(f"Gimbal demand rate: {self.thrust_vector_actuator.demand_rate} Hz")
print(
f"Gimbal range: {self.thrust_vector_actuator.actuator_range[0]:.2f} to {self.thrust_vector_actuator.actuator_range[1]:.2f} deg"
)
print(
f"Gimbal rate limit: {self.thrust_vector_actuator.actuator_rate_limit} deg/sec"
)
print(
f"Gimbal clamping: {'Enabled' if self.thrust_vector_actuator.clamp else 'Disabled'}"
)
print(
f"Gimbal time constant: {self.thrust_vector_actuator.actuator_time_constant} sec"
)
print(
f"Current gimbal angle: {self.thrust_vector_actuator.gimbal_angle:.2f} deg"
)

def all(self):
"""Prints all information of the thrust vector actuator."""
self.basics()
4 changes: 4 additions & 0 deletions rocketpy/rocket/actuator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from rocketpy.rocket.actuator.actuator import Actuator
from rocketpy.rocket.actuator.roll import RollActuator
from rocketpy.rocket.actuator.throttle import ThrottleActuator
from rocketpy.rocket.actuator.thrust_vector import ThrustVectorActuator
164 changes: 164 additions & 0 deletions rocketpy/rocket/actuator/actuator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from abc import ABC, abstractmethod

import numpy as np


class Actuator(ABC):
"""Abstract class used to define actuators.

Actuators are used to model the dynamics of control systems such as
throttle, thrust vector, and roll control. They can be used to simulate the response of
the control system to changes in throttle, thrust vector, or roll torque commands."""

def __init__(
self,
name,
demand_rate=None,
actuator_range=(-np.inf, np.inf),
actuator_rate_limit=None,
clamp=True,
actuator_initial_output=0.0,
actuator_time_constant=None,
):
"""Initializes the Actuator class.

Parameters
----------
name : str
Name of the actuator.
demand_rate : float, optional
Demand rate (Hz) of the actuator. Default is None for continuous-time actuator.
actuator_range : tuple, optional
Range of the actuator output. Default is (-np.Inf, np.Inf).
actuator_rate_limit : float, optional
Rate limit of the actuator per second. Default is None.
clamp : bool, optional
Whether to clamp the actuator output. Default is True.
actuator_initial_output : float, optional
Initial output of the actuator. Default is 0.0.
actuator_time_constant : float, optional
Time constant of the actuator, implemented as a discrete IIR filter. Default is None.

Returns
-------
None
"""

self.name = name

assert demand_rate > 0 or demand_rate is None, (
"demand_rate must be positive or None."
)
self.demand_rate = demand_rate

assert actuator_range[0] <= actuator_range[1], (
"actuator_range[0] must be <= actuator_range[1]."
)
self.actuator_range = actuator_range

assert actuator_rate_limit is None or actuator_rate_limit >= 0, (
"actuator_rate_limit must be non-negative or None."
)
self.actuator_rate_limit = actuator_rate_limit

self.clamp = clamp

assert actuator_time_constant is None or actuator_time_constant >= 0, (
"actuator_time_constant must be non-negative or None."
)
self.actuator_time_constant = actuator_time_constant
self._update_iir_coefficients()

self.actuator_initial_output = actuator_initial_output
self._actuator_output = actuator_initial_output

def _update_iir_coefficients(self):
"""Updates the IIR filter coefficient based on time constant and
demand rate. Uses first-order discrete-time system:
y[n] = alpha * u[n] + (1 - alpha) * y[n-1]
where alpha = Ts / (tau + Ts)
"""

if self.actuator_time_constant is not None and self.actuator_time_constant > 0:
if self.demand_rate is not None:
demand_period = 1.0 / self.demand_rate
self._alpha = demand_period / (
self.actuator_time_constant + demand_period
)
else:
print(
f"Warning: Actuator time constant currently only implemented on discrete controllers. '{self.name}' dynamics not applied."
)
self._alpha = 1.0 # No filtering, direct pass-through
else:
self._alpha = 1.0 # No filtering, direct pass-through

@property
def actuator_output(self):
return self._actuator_output

@actuator_output.setter
def actuator_output(self, value):
"""Sets the actuator output with optional clamping or warning.

Parameters
----------
value : float
Desired actuator output.

Returns
-------
None
"""
# Apply first-order IIR actuator dynamics
value = self._alpha * value + (1 - self._alpha) * self._actuator_output

# Apply rate limit if specified
if self.actuator_rate_limit is not None:
if self.demand_rate is not None:
max_change = self.actuator_rate_limit / self.demand_rate
change = value - self._actuator_output
if abs(change) > max_change:
value = self._actuator_output + np.sign(change) * max_change
print(
f"Warning: Actuator '{self.name}' output change {change:.3f} exceeds rate limit of {max_change:.3f} per time step."
)
else:
print(
f"Warning: Actuator rate limit currently only implemented for discrete controllers. '{self.name}' rate limit not applied."
)

# Apply range limits if specified
if self.clamp:
value = np.clip(value, self.actuator_range[0], self.actuator_range[1])
else:
if value < self.actuator_range[0] or value > self.actuator_range[1]:
print(
f"Warning: Actuator '{self.name}' output {value:.3f} exceeds range limits {self.actuator_range}."
)

self._actuator_output = value

def _reset(self):
"""Resets the actuator to its initial state. This method
is called at the beginning of each simulation to ensure the actuator
is in the correct state."""
self._actuator_output = self.actuator_initial_output

@abstractmethod
def info(self):
"""Prints summarized information of the actuator.

Returns
-------
None
"""

@abstractmethod
def all_info(self):
"""Prints all information of the actuator.

Returns
-------
None
"""
Loading
Loading