Skip to content
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

Merged
merged 15 commits into from
Oct 10, 2022
Merged
6 changes: 3 additions & 3 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Lint with flake8
run: |
pip install flake8
flake8 . --count --show-source --statistics
flake8 . --count --show-source --statistics --max-line-length=127 --ignore=E402,W503

build:

Expand All @@ -40,15 +40,15 @@ jobs:
run: |
# stop the build if there are Python syntax errors or undefined names
pip install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --max-line-length=127
# # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Install dependencies
run: |
$CONDA/bin/conda env update --file environment.yml --name base
- name: Test with pytest
run: |
cd romeos/tests
cd paseos/tests
conda install pytest
$CONDA/bin/pip3 install pytest-error-for-skips
$CONDA/bin/pytest -ra --error-for-skips
2 changes: 1 addition & 1 deletion README.md
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
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# -- Project information -----------------------------------------------------

project = "ROMEOS"
project = "PASEOS"
copyright = "2022"
author = "Pablo Gómez, Gabriele Meoni, Johan Östman, Vinutha Magal Shreenath"

Expand Down
4 changes: 2 additions & 2 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. ROMEOS documentation master file
.. PASEOS documentation master file

Welcome to ROMEOS' documentation!
Welcome to PASEOS' documentation!
================================

.. toctree::
Expand Down
2 changes: 1 addition & 1 deletion docs/source/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Overview of modules
.. toctree::
:maxdepth: 4

romeos
paseos
5 changes: 4 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: romeos
name: paseos
channels:
- conda-forge
dependencies:
- dotmap
- loguru
- pykep
- pytest
- python
- scikit-spatial
- sphinx
- sphinx_rtd_theme
- toml
Expand Down
19 changes: 19 additions & 0 deletions paseos/__init__.py
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"]
125 changes: 125 additions & 0 deletions paseos/actors/base_actor.py
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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ValueError?

Copy link
Collaborator Author

@gomezzz gomezzz Oct 10, 2022

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.

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
24 changes: 24 additions & 0 deletions paseos/actors/ground_station_actor.py
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]
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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)
28 changes: 28 additions & 0 deletions paseos/actors/spacecraft_actor.py
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)
78 changes: 78 additions & 0 deletions paseos/paseos.py
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
4 changes: 3 additions & 1 deletion romeos/tests/import_test.py → paseos/tests/import_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Trivial test to see if model import still succeeds."""

import sys

sys.path.append("../..")


def test_import():
import romeos # noqa: F401
import paseos # noqa: F401


if __name__ == "__main__":
Expand Down
Loading