Skip to content

Commit

Permalink
Adding commits according to Pablo's suggestions.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriele Meoni committed Feb 14, 2024
1 parent 1d7df19 commit 5d184cf
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 87 deletions.
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies:
- myst-parser # for markdown math in docs
- pykep>=2.6 # core non-optional dependency
- pyquaternion>=0.9.9 # core non-optional dependency
- pytest<8.0.0 # for tests
- pytest<8.0.0 # for tests. v8.0.0 is not supporting asyncio. To be checked later.
- pytest-asyncio # for tests involving activities
- python>=3.8 # core non-optional dependency
- scikit-spatial>=6.5.0 # core non-optional dependency
Expand Down
64 changes: 56 additions & 8 deletions paseos/actors/actor_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ def set_spacecraft_body_model(
actor, SpacecraftActor
), "Body model is only supported for SpacecraftActors."

logger.trace("Checking mass values for sensibility.")
assert mass > 0, "Mass is > 0"

# Check if the actor already has mass.
Expand All @@ -349,8 +350,8 @@ def set_spacecraft_body_model(
actor._spacecraft_body_model = SpacecraftBodyModel(
actor_mass=mass, vertices=vertices, faces=faces, scale=scale
)

# Check if the actor has a moment of inertia
# Logging
logger.debug(f"Added spacecraft body model to actor {actor}.")

@staticmethod
def set_power_devices(
Expand Down Expand Up @@ -564,14 +565,27 @@ def set_attitude_disturbances(
# Create a list with user specified disturbances which are considered in the attitude modelling.
disturbance_list = []

# Disturbance list name
disturbance_list_name = ""

if aerodynamic:
disturbance_list.append(TorqueDisturbanceModel.Aerodynamic)
disturbance_list_name += "Aerodynamic, "
if gravitational:
disturbance_list.append(TorqueDisturbanceModel.Gravitational)
disturbance_list_name += "Gravitational, "
if magnetic:
disturbance_list.append(TorqueDisturbanceModel.Magnetic)
# Set attitude models.
actor._attitude_model._disturbances = disturbance_list
disturbance_list_name += "Magnetic, "

if len(disturbance_list) > 0:
# Set attitude models.
actor._attitude_model._disturbances = disturbance_list
logger.debug(
f"Added {disturbance_list_name[:-2]} attitude torque disturbance models to actor {actor}."
)
else:
logger.warning("No disturbance model was specified.")

@staticmethod
def set_attitude_model(
Expand All @@ -580,16 +594,25 @@ def set_attitude_model(
actor_initial_angular_velocity: list[float] = [0.0, 0.0, 0.0],
actor_pointing_vector_body: list[float] = [0.0, 0.0, 1.0],
actor_residual_magnetic_field_body: list[float] = [0.0, 0.0, 0.0],
accommodation_coefficient: float = 0.85,
):
"""Add an attitude model to the actor based on initial conditions: attitude (roll, pitch & yaw angles)
and angular velocity vector, modeling the evolution of the user specified pointing vector.
Args:
actor (SpacecraftActor): Actor to model.
actor_initial_attitude_in_rad (list of floats): Actor's initial attitude. Defaults to [0.0, 0.0, 0.0].
actor_initial_angular_velocity (list of floats): Actor's initial angular velocity. Defaults to [0.0, 0.0, 0.0].
actor_pointing_vector_body (list of floats): Actor's pointing vector with respect to the body frame. Defaults to [0.0, 0.0, 1.0].
actor_residual_magnetic_field_body (list of floats): Actor's residual magnetic dipole moment vector in the body frame. Only needed if magnetic torque disturbances are modelled. Please, refer to [Tai L. Chow (2006) p. 148 - 149]. Defaults to [0.0, 0.0, 0.0].
actor_initial_attitude_in_rad (list of floats): Actor's initial attitude.
Defaults to [0.0, 0.0, 0.0].
actor_initial_angular_velocity (list of floats): Actor's initial angular velocity.
Defaults to [0.0, 0.0, 0.0].
actor_pointing_vector_body (list of floats): Actor's pointing vector with respect to the body frame.
Defaults to [0.0, 0.0, 1.0].
actor_residual_magnetic_field_body (list of floats): Actor's residual magnetic dipole moment vector
in the body frame. Only needed if magnetic torque disturbances are modelled.
Please, refer to [Tai L. Chow (2006) p. 148 - 149]. Defaults to [0.0, 0.0, 0.0].
accommodation_coefficient (float): Accommodation coefficient used for Aerodynamic torque disturbance calculation.
Defaults to 0.85.
"""
# check for spacecraft actor
assert isinstance(
Expand All @@ -602,14 +625,39 @@ def set_attitude_model(
"The actor already had an attitude model. Overriding old attitude model."
)

assert (
np.asarray(actor_initial_attitude_in_rad).shape[0] == 3
and actor_initial_attitude_in_rad.ndim == 1
), "actor_initial_attitude_in_rad shall be [3] shaped."

assert (
np.asarray(actor_initial_angular_velocity).shape[0] == 3
and actor_initial_angular_velocity.ndim == 1
), "actor_initial_angular_velocity shall be [3] shaped."

assert (
np.asarray(actor_pointing_vector_body).shape[0] == 3
and actor_pointing_vector_body.ndim == 1
), "actor_pointing_vector_body shall be [3] shaped."

assert (
np.asarray(actor_residual_magnetic_field_body).shape[0] == 3
and actor_residual_magnetic_field_body.ndim == 1
), "actor_residual_magnetic_field_body shall be [3] shaped."

logger.trace("Checking accommodation coefficient for sensibility.")
assert accommodation_coefficient > 0, "Accommodation coefficient shall be positive."

# Set attitude model.
actor._attitude_model = AttitudeModel(
local_actor=actor,
actor_initial_attitude_in_rad=actor_initial_attitude_in_rad,
actor_initial_angular_velocity=actor_initial_angular_velocity,
actor_pointing_vector_body=actor_pointing_vector_body,
actor_residual_magnetic_field_body=actor_residual_magnetic_field_body,
accommodation_coefficient=accommodation_coefficient,
)
logger.debug(f"Added attitude model to actor {actor}.")

@staticmethod
def add_comm_device(actor: BaseActor, device_name: str, bandwidth_in_kbps: float):
Expand Down
56 changes: 55 additions & 1 deletion paseos/actors/spacecraft_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class SpacecraftActor(BaseActor):
# If radiation permanently killed this device
_is_dead = False

# Default temperature [K]
_default_temperature_in_K = 300

def __init__(
self,
name: str,
Expand All @@ -53,6 +56,48 @@ def set_was_interrupted(self):
"""Sets this device to "was_interrupted=True" indicating current activities were interrupted."""
self._was_interrupted = True

def set_body_attitude(self, attitude_in_rad):
"""Sets the spacecraft attitude in [yaw, pitch, roll] angles.
Args:
actor_attitude_in_rad (numpy array): actor's attitude in [yaw, pitch, roll] angles [rad].
"""
assert self.has_attitude_model, "The actor has no attitude model."
assert (
isinstance(attitude_in_rad, np.array)
and attitude_in_rad.shape[1] == 3
and attitude_in_rad.ndim == 1
), "attitude_in_rad shall be a numpy array [3] shaped."
self._attitude_model._actor_attitude_in_rad = attitude_in_rad

def set_body_pointing_vector(self, pointing_vector_body):
"""Sets the spacecraft angular velocity in body frame.
Args:
pointing_vector_body (numpy array): actor's pointing vector in body frame [m].
"""
assert self.has_attitude_model, "The actor has no attitude model."
assert (
isinstance(pointing_vector_body, np.array)
and pointing_vector_body.shape[1] == 3
and pointing_vector_body.ndim == 1
), "pointing_vector_body shall be a numpy array [3] shaped."
self._attitude_model._actor_pointing_vector_body = pointing_vector_body

def set_body_angular_velocity(self, angular_velocity_body):
"""Sets the spacecraft angular velocity in body frame.
Args:
angular_velocity_body (numpy array): actor's angular velocity in body frame [rad/s].
"""
assert self.has_attitude_model, "The actor has no attitude model."
assert (
isinstance(angular_velocity_body, np.array)
and angular_velocity_body.shape[1] == 3
and angular_velocity_body.ndim == 1
), "angular_velocity_body shall be a numpy array [3] shaped."
self._attitude_model._actor_angular_velocity = angular_velocity_body

@property
def was_interrupted(self) -> bool:
"""Returns whether the actor was interrupted in its activity.
Expand Down Expand Up @@ -134,6 +179,15 @@ def temperature_in_K(self) -> float:
"""
return self._thermal_model.temperature_in_K

@property
def default_temperature_in_K(self) -> float:
"""Returns the default temperature of the actor in K.
Returns:
float: Actor default temperature in Kelvin.
"""
return self._default_temperature_in_K

@property
def temperature_in_C(self) -> float:
"""Returns the current temperature of the actor in C.
Expand Down Expand Up @@ -250,4 +304,4 @@ def body_center_of_gravity_body(self) -> np.array:
Returns:
np.array: Coordinates of the center of gravity of the mesh.
"""
return self._spacecraft_body_model._body_center_of_gravity_body
return self._spacecraft_body_model._body_center_of_gravity_body
44 changes: 42 additions & 2 deletions paseos/actors/spacecraft_body_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from loguru import logger
import numpy as np
import trimesh
import itertools


class SpacecraftBodyModel:
Expand Down Expand Up @@ -35,6 +36,45 @@ def __init__(self, actor_mass, vertices=None, faces=None, scale=1) -> None:
self._body_moment_of_inertia_body = self._body_mesh.moment_inertia * self._body_mass
self._body_center_of_gravity_body = self._body_mesh.center_mass

@staticmethod
def _is_cuboid(vertices):
"""Check if the mesh corresponds to a cuboid.
Args:
vertices (list): List of all vertices of the mesh in terms of distance (in m) from origin of body frame.
Returns:
bool: True, if the mesh corresponds to a cuboid.
"""
# Convert to numpy
vertices = np.array(vertices)
# If the len is not 8, then it is not a cuboid.
if len(vertices) != 8:
return False

# Vertices distances
distances = []
# Getting distance of vertices
for v1, v2 in itertools.combinations(vertices, 2):
distances.append(np.linalg.norm(v1 - v2))

# Unique distances
unique_distances = set(distances)
# If the unique distances are not 2 nor 3, then it is not a cuboid.
if len(unique_distances) not in [2, 3]:
return False

# Count the occurrences of each distance
distance_counts = [distances.count(d) for d in unique_distances]

# For a cuboid, there should be 12 edges of one length, 12 edges of root 2 of that length,
# and 4 edges of root 3 of that length.
# For a cube, there should be 12 edges of one length, and 4 edges of root 3 of that length.
distance_counts.sort()
if distance_counts not in [[12, 4], [4, 12, 12]]:
return False

return True

def _create_body_mesh(self, vertices=None, faces=None, scale=1):
"""Creates the mesh of the satellite. If no vertices input is given, it defaults to a cuboid scaled by the
scale value. The default without scale values is a cube with 1m sides. This uses the python module Trimesh.
Expand Down Expand Up @@ -83,6 +123,8 @@ def _create_body_mesh(self, vertices=None, faces=None, scale=1):
assert (
len(np.asarray(vertices).shape) == 2 and np.asarray(vertices).shape[1] == 3
), "Vertices shall be [N, 3] shaped."
if not (SpacecraftBodyModel._is_cuboid(vertices=vertices)):
raise NotImplementedError("Only cuboid meshes are currently supported.")

# Create mesh
logger.trace("Creating the spacecraft body mesh.")
Expand All @@ -98,5 +140,3 @@ def body_mesh(self) -> np.array:
np.array: Mesh of the satellite.
"""
return self._body_mesh


Loading

0 comments on commit 5d184cf

Please sign in to comment.