-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First steps module implementation #3
Changes from all commits
1a1c42d
2f0ffc9
8034a89
10c6806
1a8b3f9
5b3480c
daa586d
fcb8804
08be3ad
02dde41
dcb350a
d2401b0
35d342f
7c666fd
1211ad4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# PASEOS | ||
PASEOS - PAseos Simulates the Environment for Operating multiple Spacecraft | ||
PASEOS - PAseos Simulates the Environment for Operating multiple Spacecraft |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,4 @@ Overview of modules | |
.. toctree:: | ||
:maxdepth: 4 | ||
|
||
romeos | ||
paseos |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from loguru import logger | ||
|
||
from .utils.set_log_level import set_log_level | ||
from .paseos import PASEOS | ||
from .actors.spacecraft_actor import SpacecraftActor | ||
from .actors.ground_station_actor import GroundstationActor | ||
|
||
set_log_level("DEBUG") | ||
|
||
logger.debug("Loaded module.") | ||
|
||
|
||
def init_sim(): | ||
logger.debug("Initializing simulation.") | ||
sim = PASEOS() | ||
return sim | ||
|
||
|
||
__all__ = ["GroundstationActor", "SpacecraftActor"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
from loguru import logger | ||
import pykep as pk | ||
|
||
from skspatial.objects import Line, Sphere | ||
|
||
from abc import ABC | ||
|
||
|
||
class BaseActor(ABC): | ||
"""This (abstract) class is the baseline implementation of an actor | ||
(e.g. spacecraft, ground station) in the simulation. The abstraction allows | ||
some actors to have e.g. limited power (spacecraft) and others not to. | ||
""" | ||
|
||
# Actor name, has to be unique | ||
name = None | ||
|
||
# Orbital parameters of the actor, stored in a pykep planet object | ||
_orbital_parameters = None | ||
|
||
# Constraint for max bandwidth used when comms are available | ||
_max_bandwidth_kbps = None | ||
|
||
# Earth as a sphere (for now) | ||
# TODO replace this in the future depending on central body | ||
_central_body_sphere = Sphere([0, 0, 0], 6371000) | ||
|
||
def __init__( | ||
self, name: str, position, velocity, epoch: pk.epoch, central_body: pk.planet | ||
) -> None: | ||
"""Constructor for a base actor | ||
|
||
Args: | ||
name (str): Name of this actor | ||
position (list of floats): [x,y,z] | ||
velocity (list of floats): [vx,vy,vz] | ||
epoch (pykep.epoch): Epoch at this pos / velocity | ||
central_body (pk.planet): pykep central body | ||
""" | ||
logger.trace("Instantiating Actor.") | ||
super().__init__() | ||
self.name = name | ||
self._orbital_parameters = pk.planet.keplerian( | ||
epoch, | ||
position, | ||
velocity, | ||
central_body.mu_self, | ||
1.0, | ||
1.0, | ||
1.0, | ||
name, | ||
) | ||
|
||
def __str__(self): | ||
return self._orbital_parameters.name | ||
|
||
def get_position_velocity(self, epoch: pk.epoch): | ||
logger.trace( | ||
"Computing " | ||
+ self._orbital_parameters.name | ||
+ " position / velocity at time " | ||
+ str(epoch.mjd2000) | ||
+ " (mjd2000)." | ||
) | ||
return self._orbital_parameters.eph(epoch) | ||
|
||
def is_in_line_of_sight( | ||
self, other_actor: "BaseActor", epoch: pk.epoch, plot=False | ||
): | ||
"""Determines whether a position is in line of sight of this actor | ||
|
||
Args: | ||
other_actor (BaseActor): The actor to check line of sight with | ||
epoch (pk,.epoch): Epoch at which to check the line of sight | ||
plot (bool): Whether to plot a diagram illustrating the positions. | ||
|
||
Returns: | ||
bool: true if in line-of-sight. | ||
""" | ||
logger.debug( | ||
"Computing line of sight between actors: " | ||
+ str(self) | ||
+ " " | ||
+ str(other_actor) | ||
) | ||
my_pos, _ = self.get_position_velocity(epoch) | ||
other_actor_pos, _ = other_actor.get_position_velocity(epoch) | ||
|
||
logger.trace( | ||
"Computed positions for actors are " | ||
+ str(my_pos) | ||
+ " and " | ||
+ str(other_actor_pos) | ||
) | ||
line_between_actors = Line( | ||
my_pos, | ||
[ | ||
other_actor_pos[0] - my_pos[0], | ||
other_actor_pos[1] - my_pos[1], | ||
other_actor_pos[2] - my_pos[2], | ||
], | ||
) | ||
print(line_between_actors) | ||
if plot: | ||
from skspatial.plotting import plot_3d | ||
|
||
# Currently skspatial throws a ValueError if there is no intersection so we have to use this rather ugly way. | ||
try: | ||
p1, p2 = self._central_body_sphere.intersect_line(line_between_actors) | ||
logger.trace("Intersections observed at " + str(p1) + " and " + str(p2)) | ||
if plot: | ||
plot_3d( | ||
line_between_actors.plotter(t_1=0, t_2=1, c="k"), | ||
self._central_body_sphere.plotter(alpha=0.2), | ||
p1.plotter(c="r", s=100), | ||
p2.plotter(c="r", s=100), | ||
) | ||
except ValueError: | ||
if plot: | ||
plot_3d( | ||
line_between_actors.plotter(t_1=0, t_2=1, c="k"), | ||
self._central_body_sphere.plotter(alpha=0.2), | ||
) | ||
return True | ||
return False |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from loguru import logger | ||
import pykep as pk | ||
|
||
from paseos.actors.base_actor import BaseActor | ||
|
||
|
||
class GroundstationActor(BaseActor): | ||
"""This class models a groundstation actor.""" | ||
|
||
def __init__( | ||
self, name: str, position, velocity, epoch: pk.epoch, central_body: pk.planet | ||
) -> None: | ||
"""Constructor for a groundstation actor. | ||
Pos / velocity are relative to central body origin. | ||
|
||
Args: | ||
name (str): Name of this actor | ||
position (list of floats): [x,y,z] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks odd that Groundstation has a "velocity". I guess you are doing that because our (0,0,0) is not necessarily the ground station. Maybe, add a comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everything is in central body frame, the ground station is "orbiting" it with the Earth's (or other body's) rotation as velocity and position relative to the center. Added a comment to clarify |
||
velocity (list of floats): [vx,vy,vz] | ||
epoch (pykep.epoch): Epoch at this pos / velocity | ||
central_body (pk.planet): pykep central body | ||
""" | ||
logger.trace("Instantiating GroundstationActor.") | ||
super().__init__(name, position, velocity, epoch, central_body) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from loguru import logger | ||
import pykep as pk | ||
|
||
from paseos.actors.base_actor import BaseActor | ||
|
||
|
||
class SpacecraftActor(BaseActor): | ||
"""This class models a spacecraft actor which in addition to pos, | ||
velocity also has additional constraints such as power/battery.""" | ||
|
||
# Power constraints | ||
# TODO | ||
_available_power = None | ||
|
||
def __init__( | ||
self, name: str, position, velocity, epoch: pk.epoch, central_body: pk.planet | ||
) -> None: | ||
"""Constructor for a spacecraft actor | ||
|
||
Args: | ||
name (str): Name of this actor | ||
position (list of floats): [x,y,z] | ||
velocity (list of floats): [vx,vy,vz] | ||
epoch (pykep.epoch): Epoch at this pos / velocity | ||
central_body (pk.planet): pykep central body | ||
""" | ||
logger.trace("Instantiating SpacecraftActor.") | ||
super().__init__(name, position, velocity, epoch, central_body) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from dotmap import DotMap | ||
from loguru import logger | ||
import pykep as pk | ||
|
||
from paseos.actors.base_actor import BaseActor | ||
|
||
from .utils.load_default_cfg import load_default_cfg | ||
|
||
|
||
class PASEOS: | ||
"""This class serves as the main interface with the user. It is designed | ||
as a singleton to ensure we only have one instance running at any time.""" | ||
|
||
# Config file of the simulation | ||
_cfg = None | ||
|
||
# Stores the simulation state | ||
_state = None | ||
|
||
def __new__(self): | ||
if not hasattr(self, "instance"): | ||
self.instance = super(PASEOS, self).__new__(self) | ||
else: | ||
logger.warning( | ||
"Tried to create another instance of PASEOS simulation.Keeping original one..." | ||
) | ||
return self.instance | ||
|
||
def __init__(self): | ||
logger.trace("Initializing PASEOS") | ||
self._cfg = load_default_cfg() | ||
self._state = DotMap(_dynamic=False) | ||
self._state.time = 0 | ||
self._state.actors = [] | ||
|
||
def advance_time(self, dt: float): | ||
"""Advances the simulation by a specified amount of time | ||
|
||
Args: | ||
dt (float): Time to advance in seconds | ||
""" | ||
logger.debug("Advancing time by " + str(dt) + " s.") | ||
self._state.time += dt | ||
|
||
logger.debug("New time is: " + str(self._state.time) + " s.") | ||
|
||
def add_actor(self, actor: BaseActor): | ||
"""Adds an actor to the simulation. | ||
|
||
Args: | ||
actor (BaseActor): Actor to add | ||
""" | ||
logger.debug("Adding actor:" + str(actor)) | ||
# Check for duplicate actors by name | ||
for existing_actor in self._state.actors: | ||
if existing_actor.name == actor.name: | ||
raise ValueError( | ||
"Trying to add actor with already existing name: " + actor.name | ||
) | ||
# Else add | ||
self._state.actors.append(actor) | ||
|
||
def set_central_body(self, planet: pk.planet): | ||
"""Sets the central body of the simulation for the orbit simulation | ||
|
||
Args: | ||
planet (pk.planet): The central body as a pykep planet | ||
""" | ||
logger.debug("Setting central body to " + planet) | ||
self._state.central_body = planet | ||
|
||
def get_cfg(self) -> DotMap: | ||
"""Returns the current cfg of the simulation | ||
|
||
Returns: | ||
DotMap: cfg | ||
""" | ||
return self._cfg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ValueError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment in L107
# Currently skspatial throws a ValueError if there is no intersection so we have to use this rather ugly way.