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

Comms expansion #209

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1ed3e59
Added models
Jan 17, 2024
abd9fcd
Added additional calculations
Jan 17, 2024
8ee579b
Added tests for communication models and calculations
Jan 17, 2024
4c376af
Fix typo
Feb 6, 2024
0d7298e
Fixed typo
Feb 6, 2024
b840d2c
Comments and cleanup
Feb 7, 2024
7b31547
Tests refactoring
Feb 7, 2024
00da78e
Update
Feb 7, 2024
02f710e
Update
Feb 7, 2024
c0a1c27
Merge branch 'main' into comms_expansion
Feb 7, 2024
ebb0e79
Fixed circular reference errors
Feb 8, 2024
09079cb
Added .idea to gitignore
Feb 8, 2024
4177086
Bugfix after the refactoring
Feb 8, 2024
28ed261
Refactoring
Feb 8, 2024
f6e1de5
Applied black formatting
Feb 8, 2024
c31c38c
Black format attempt 2
Feb 8, 2024
68296cf
Flake8 attempt 1
Feb 8, 2024
c9077b5
Flake8 attempt 2
Feb 8, 2024
5526e95
Black formatting attempt 3
Feb 8, 2024
b9dc375
Add pylintrc to gitignore
Feb 8, 2024
9e31d2e
Delete pylintrc
timsmitdelft Feb 8, 2024
6a5352c
Merge branch 'comms_expansion' of https://github.com/aidotse/PASEOS i…
Feb 8, 2024
8481664
Docstrings
Feb 26, 2024
76c7a47
Updated readme with comms link budget example
Feb 26, 2024
c6311f3
Removed one layer of abstraction
Feb 26, 2024
16c82c9
Updated example notebooks
Feb 26, 2024
ca5ac42
Added constants
Feb 26, 2024
ef5dbb7
Moved comms utils to comms folder
Feb 26, 2024
9bff5eb
Moved comm links to actor instead of paseos
Feb 26, 2024
0583471
Updated comms tests
Feb 26, 2024
1b5d536
Moved tests
Feb 26, 2024
2082b42
Formatting
Feb 26, 2024
51a2dc3
Black formatting with line length 100
Feb 26, 2024
991968d
Improved docstrings
Feb 26, 2024
90af927
Added comments to tests
Feb 26, 2024
44a2ead
Allow radio receiver on satellite
Feb 27, 2024
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
Prev Previous commit
Next Next commit
Refactoring
  • Loading branch information
Tim Smit committed Feb 8, 2024
commit 28ed2616450b956ea5123092e0caf9d46ba0728e
1 change: 0 additions & 1 deletion paseos/actors/actor_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from dotmap import DotMap
import pykep as pk
from skyfield.api import wgs84
import math

from .base_actor import BaseActor
from .spacecraft_actor import SpacecraftActor
Expand Down
60 changes: 32 additions & 28 deletions paseos/communication/link_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..actors.base_actor import BaseActor
import math


class LinkModel:
"""This class defines a link model, containing one transmitter and one receiver."""

Expand All @@ -18,27 +19,29 @@ class LinkModel:
_current_line_of_sight = False
_current_distance = 0
_current_elevation_angle = 0

def __init__(
self,
transmitter_actor: BaseActor,
transmitter_model: TransmitterModel,
receiver_actor: BaseActor,
receiver_model: ReceiverModel,
frequency: float
self,
transmitter_actor: BaseActor,
transmitter_model: TransmitterModel,
receiver_actor: BaseActor,
receiver_model: ReceiverModel,
frequency: float
) -> None:
"""Initializes the model.

Args:
transmitter_actor (BaseActor): The transmitter in this link.
receiver (ReceiverModel): The receiver in this link.
transmitter_actor (BaseActor): the transmitter actor in this link.
transmitter_model (TransmitterModel): the transmitter device model in this link.
receiver_actor (BaseActor): the receiver actor in this link.
receiver_model (ReceiverModel): the receiver device model.
"""
# assert isinstance(transmitter, TransmitterModel), "A transmitter is required for this link."
assert isinstance(receiver_model, ReceiverModel), "A receiver is required for this link."

logger.debug("Initializing link model.")
self.c = 299792458
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved
self.wavelength = self.c / frequency # in m
self.wavelength = self.c / frequency # in m
self.transmitter_actor = transmitter_actor
self.transmitter = transmitter_model
self.receiver_actor = receiver_actor
Expand All @@ -47,30 +50,31 @@ def __init__(
self._line_of_sight_history = []
self._distance_history = []
self._elevation_angle_history = []
def get_path_loss(self, slant_range):

def get_path_loss(self, slant_range: float) -> float:
"""Gets the path loss (free space loss) for a link.

Args:
slant_range (int): The slant range of the link, in meters
slant_range (float): The slant range of the link, in meters
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved

Returns:
The path loss (free space loss) in dB
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved
"""
assert slant_range > 0, "Slant range needs to be higher than 0 meters"

return 20 * math.log10(4 * math.pi * slant_range / self.wavelength)
return 20 * math.log10(4 * math.pi * slant_range / self.wavelength)

def set_bitrate(self, bitrate: float):
def set_bitrate(self, bitrate: float) -> None:
"""Sets the bitrate of this link for a certain epoch.

Args:
bitrate (float): The bitrate of this link, in bps
"""
self._current_bitrate = bitrate

def set_line_of_sight(self, state: bool):
"""Sets the line of sight of this link for a certain epoch, if there is a line of sight, the transmitter is set to active.

def set_line_of_sight(self, state: bool) -> None:
"""Sets the line of sight of this link for a certain epoch,
if there is a line of sight, the transmitter is set to active.

Args:
state (bool): The current line of sight state
Expand All @@ -81,41 +85,41 @@ def set_line_of_sight(self, state: bool):
else:
self.transmitter.set_active(False)

def set_distance(self, distance: float):
def set_distance(self, distance: float) -> None:
"""Sets the distance of this link for a certain epoch.

Args:
distance (float): The slant range of the link, in meters
"""
self._current_distance = distance
def set_elevation_angle(self, angle: float):

def set_elevation_angle(self, angle: float) -> None:
"""Sets the elevation angle of this link for a certain epoch.

Args:
angle (float): The elevation angle, in degrees
"""
self._current_elevation_angle = angle
def save_state(self):

def save_state(self) -> None:
"""Saves the state of this link."""
self._bitrate_history.append(self._current_bitrate)
self._line_of_sight_history.append(self._current_line_of_sight)
self._distance_history.append(self._current_distance)
self._elevation_angle_history.append(self._current_elevation_angle)

@property
def bitrate_history(self):
return self._bitrate_history

@property
def line_of_sight_history(self):
return self._line_of_sight_history

@property
def distance_history(self):
return self._distance_history

@property
def elevation_angle_history(self):
return self._elevation_angle_history
return self._elevation_angle_history
49 changes: 26 additions & 23 deletions paseos/communication/optical_link_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
from ..actors.base_actor import BaseActor
import math


class OpticalLinkModel(LinkModel):
"""This class defines an ptical link model, containing one transmitter and one receiver."""
"""This class defines an optical link model, containing one transmitter and one receiver."""

def __init__(
self,
transmitter_actor: BaseActor,
transmitter_device_name: str,
receiver_actor: BaseActor,
receiver_device_name: str
self,
transmitter_actor: BaseActor,
transmitter_device_name: str,
receiver_actor: BaseActor,
receiver_device_name: str
) -> None:
"""Initializes the model.

Expand All @@ -25,44 +26,46 @@ def __init__(
receiver_actor (BaseActor): the receiver in this link.
receiver_device_name (str): the name of the receiver device.
"""
self.wavelength = 1550E-9 # in m
self.wavelength = 1550E-9 # in m

# Get the transmitter and receiver models from the actor
transmitter = transmitter_actor.get_transmitter(transmitter_device_name)
receiver = receiver_actor.get_receiver(receiver_device_name)

super().__init__(transmitter_actor, transmitter, receiver_actor, receiver, frequency=299792458/self.wavelength)

assert isinstance(transmitter, OpticalTransmitterModel), "An optical transmitter is required for this optical link."
super().__init__(transmitter_actor, transmitter, receiver_actor, receiver,
frequency=299792458 / self.wavelength)

assert isinstance(transmitter, OpticalTransmitterModel), ("An optical transmitter is required "
"for this optical link.")
assert isinstance(receiver, OpticalReceiverModel), "An optical receiver is required for this optical link."

logger.debug("Initializing optical link model.")
self.required_BER = 10E-3

self.modulation_scheme = "OOK"
self.required_s_n_margin = 3 # in dB
self.required_s_n_margin = 3 # in dB

self.receiver.set_gain(self.wavelength)
self.transmitter.set_gain()
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved
def get_path_loss(self, slant_range):

def get_path_loss(self, slant_range: float) -> float:
"""Gets the path loss (free space loss) for a link.

Args:
slant_range (int): The slant range of the link, in meters
slant_range (float): The slant range of the link, in meters

Returns:
The path loss (free space loss) in dB
"""
assert slant_range > 0, "Slant range needs to be higher than 0 meters"

return 20 * math.log10(4 * math.pi * slant_range / self.wavelength)
def get_bitrate(self, slant_range, min_elevation_angle):
return 20 * math.log10(4 * math.pi * slant_range / self.wavelength)

def get_bitrate(self, slant_range: float, min_elevation_angle: float) -> float:
"""Gets the bitrate for a link based on current slant range and minimum elevation angle.
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved

Args:
slant_range (int): The slant range of the link, in meters
slant_range (float): The slant range of the link, in meters
min_elevation_angle (float): The minimum elevation angle for this receiver, in degrees

Returns:
Expand All @@ -76,11 +79,11 @@ def get_bitrate(self, slant_range, min_elevation_angle):
self.signal_at_receiver = self.transmitter.EIRP - self.total_channel_loss
Copy link
Collaborator

Choose a reason for hiding this comment

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

can this become negative? what happens?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are those values on a logarithmic scale? If yes, please, specify it in the names (e.g., EIRP_dB or something like that).

Copy link
Collaborator

Choose a reason for hiding this comment

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

If these are in logarithmic scales, that means the loss of your signal will reduce your power to 0 dB. The effect of that depends on the noise power. If the noise power is higher than 0 dB, then you will have a terrible Bit Error Rate.


self.received_signal_power_with_gain = self.signal_at_receiver + self.receiver.antenna_gain - self.receiver.line_losses
self.received_signal_power_with_margin = self.received_signal_power_with_gain - self.required_s_n_margin #dBm
self.received_signal_power_with_margin = 10**(self.received_signal_power_with_margin / 10) * 1E-3 #nW
self.received_signal_power_with_margin = self.received_signal_power_with_gain - self.required_s_n_margin # dBm
self.received_signal_power_with_margin = 10 ** (self.received_signal_power_with_margin / 10) * 1E-3 # nW
bitrate = self.received_signal_power_with_margin / 250 * 1550E-9 / 6.626E-34 / self.c

if bitrate < 0:
bitrate = 0
return bitrate

return bitrate
21 changes: 12 additions & 9 deletions paseos/communication/optical_receiver_model.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
from loguru import logger
from .receiver_model import ReceiverModel
from ..utils.gain_calc import calc_optical_gain_from_wavelength_diameter


class OpticalReceiverModel(ReceiverModel):
"""This class defines an optical receiver model."""

def __init__(
self,
line_losses: int,
antenna_diameter: int = 0,
antenna_gain: int = 0
self,
line_losses: float,
antenna_diameter: float = 0,
antenna_gain: float = 0
) -> None:
"""Initializes the model.

Args:
line_losses (int): The line losses of the receiver, in dB.
antenna_diameter (int): The diameter of the antenna, in m. Either this or the gain needs to be given.
antenna_gain (int): The gain of the antenna, either this or the diameter needs to be given so that gain can be determined.
line_losses (float): The line losses of the receiver, in dB.
antenna_diameter (float): The diameter of the antenna, in m. Either this or the gain needs to be given.
antenna_gain (float): The gain of the antenna, either this or the diameter needs to be given so that
gain can be determined.

"""

super().__init__(line_losses, antenna_diameter, antenna_gain)
logger.debug("Initializing optical receiver model.")

def set_gain(self, wavelength):
def set_gain(self, wavelength: float = 0) -> None:
"""Sets gain for a receiver, based on the given gain, or antenna diameter and wavelength.

Args:
wavelength (int): The wavelength of the link, in meters
"""
if self.antenna_gain == 0:
self.antenna_gain = calc_optical_gain_from_wavelength_diameter(wavelength, self.antenna_diameter, 1)
self.antenna_gain = calc_optical_gain_from_wavelength_diameter(wavelength, self.antenna_diameter, 1)
29 changes: 13 additions & 16 deletions paseos/communication/optical_transmitter_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,34 @@ class OpticalTransmitterModel(TransmitterModel):

def __init__(
self,
input_power: int,
input_power: float,
power_efficiency: float,
antenna_efficiency: float,
line_losses: int,
point_losses: int,
antenna_gain: int = 0,
antenna_diameter: int = 0,
fwhm: int = 0
line_losses: float,
point_losses: float,
antenna_gain: float = 0,
antenna_diameter: float = 0,
fwhm: float = 0
) -> None:
"""Initializes the model.

Args:
input_power (int): Input power into the signal amplifier, in W.
input_power (float): Input power into the signal amplifier, in W.
power_efficiency (float): The power efficiency of the signal amplifier, determines the output power.
antenna_efficiency (float): The efficiency of the antenna.
line_losses (int): The line losses of the transmitter, in dB.
point_losses (int): The pointing losses of the transmitter, in dB.
antenna_gain (int): The gain of the antenna, either this or the diameter needs to be given so that gain
line_losses (float): The line losses of the transmitter, in dB.
point_losses (float): The pointing losses of the transmitter, in dB.
antenna_gain (float): The gain of the antenna, either this or the diameter needs to be given so that gain
can be determined.
antenna_diameter (int): The diameter of the antenna, in m. Either this or the gain needs to be given.
fwhm (int): full width at half maximum, in radians.
antenna_diameter (float): The diameter of the antenna, in m. Either this or the gain needs to be given.
fwhm (float): full width at half maximum, in radians.
"""

logger.debug("Initializing optical transmitter model.")
super().__init__(input_power, power_efficiency, antenna_efficiency, line_losses, point_losses, antenna_gain,
antenna_diameter)
assert antenna_gain > 0 or antenna_diameter > 0 or fwhm > 0, ("Antenna gain or antenna diameter or "
"FWHM needs to be higher than 0.")
# assert (
# antenna_diameter > 0 and antenna_gain > 0 and fwhm > 0), ("Only set one of antenna gain, "
# "antenna diameter, and FWHM not multiple.")
assert sum(param != 0 for param in (antenna_diameter, antenna_gain,
fwhm)) <= 1, ("Only set one of antenna gain, "
"antenna diameter, and FWHM not multiple.")
Expand All @@ -49,7 +46,7 @@ def __init__(
self.output_power = 10 * math.log10(input_power * power_efficiency * 1000) # dBm
self.FWHM = fwhm
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved

def set_gain(self):
def set_gain(self) -> None:
"""Sets gain for a transmitter, based on the given gain, or antenna diameter and wavelength.
"""
if self.antenna_gain == 0:
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
10 changes: 5 additions & 5 deletions paseos/communication/radio_link_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ def __init__(
self.receiver.set_gain(self.wavelength)
self.transmitter.set_gain(self.wavelength)

def get_path_loss(self, slant_range) -> float:
def get_path_loss(self, slant_range: float) -> float:
"""Gets the path loss (free space loss) for a link.

Args:
slant_range (int): The slant range of the link, in meters
slant_range (float): The slant range of the link, in meters

Returns:
The path loss (free space loss) in dB
Expand All @@ -67,7 +67,7 @@ def get_path_loss(self, slant_range) -> float:

return 20 * math.log10(4 * math.pi * slant_range / self.wavelength)

def get_max_atmospheric_loss(self, min_elevation_angle) -> float:
def get_max_atmospheric_loss(self, min_elevation_angle: float) -> float:
"""Gets the maximal atmospheric loss for a link.

Args:
Expand All @@ -80,11 +80,11 @@ def get_max_atmospheric_loss(self, min_elevation_angle) -> float:

return self.zenith_atmospheric_attenuation / math.sin(min_elevation_angle * math.pi / 180)

def get_bitrate(self, slant_range, min_elevation_angle) -> float:
def get_bitrate(self, slant_range: float, min_elevation_angle: float) -> float:
"""Gets the bitrate for a link based on current slant range and minimum elevation angle.
timsmitdelft marked this conversation as resolved.
Show resolved Hide resolved

Args:
slant_range (int): The slant range of the link, in meters
slant_range (float): The slant range of the link, in meters
min_elevation_angle (float): The minimum elevation angle for this receiver, in degrees

Returns:
Expand Down
Loading
Loading